2683 lines
85 KiB
C++
2683 lines
85 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "StaticMeshEditorSubsystem.h"
|
|
|
|
#include "ActorEditorUtils.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Components/MeshComponent.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "Editor.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "EditorFramework/AssetImportData.h"
|
|
#include "EngineUtils.h"
|
|
#include "Engine/Brush.h"
|
|
#include "Engine/Selection.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "FbxMeshUtils.h"
|
|
#include "FileHelpers.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "IMeshMergeUtilities.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Kismet2/ComponentEditorUtils.h"
|
|
#include "LevelEditorViewport.h"
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshComponentLODInfo.h"
|
|
#include "StaticMeshOperations.h"
|
|
#include "MeshMergeModule.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "Algo/AllOf.h"
|
|
#include "Algo/AnyOf.h"
|
|
#include "Async/Async.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
|
|
#include "StaticMeshResources.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "GeomFitUtils.h"
|
|
#include "ConvexDecompTool.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "Subsystems/UnrealEditorSubsystem.h"
|
|
#include "Layers/LayersSubsystem.h"
|
|
#include "EditorScriptingHelpers.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(StaticMeshEditorSubsystem)
|
|
|
|
#define LOCTEXT_NAMESPACE "StaticMeshEditorSubsystem"
|
|
|
|
DEFINE_LOG_CATEGORY(LogStaticMeshEditorSubsystem);
|
|
|
|
namespace InternalEditorMeshLibrary
|
|
{
|
|
/** Note: This method is a replicate of FStaticMeshEditor::DoDecomp */
|
|
bool GenerateConvexCollision(UStaticMesh* StaticMesh, uint32 HullCount, int32 MaxHullVerts, uint32 HullPrecision)
|
|
{
|
|
// Check we have a valid StaticMesh
|
|
if (!StaticMesh || !StaticMesh->IsMeshDescriptionValid(0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateConvexCollision)
|
|
|
|
// If RenderData has not been computed yet, do it
|
|
if (!StaticMesh->GetRenderData())
|
|
{
|
|
StaticMesh->CacheDerivedData();
|
|
}
|
|
|
|
const FStaticMeshLODResources& LODModel = StaticMesh->GetRenderData()->LODResources[0];
|
|
|
|
// Make vertex buffer
|
|
int32 NumVerts = LODModel.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices();
|
|
TArray<FVector3f> Verts;
|
|
Verts.Reserve(NumVerts);
|
|
for (int32 i = 0; i < NumVerts; i++)
|
|
{
|
|
Verts.Add(LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(i));
|
|
}
|
|
|
|
// Grab all indices
|
|
TArray<uint32> AllIndices;
|
|
LODModel.IndexBuffer.GetCopy(AllIndices);
|
|
|
|
// Only copy indices that have collision enabled
|
|
TArray<uint32> CollidingIndices;
|
|
for (const FStaticMeshSection& Section : LODModel.Sections)
|
|
{
|
|
if (Section.bEnableCollision)
|
|
{
|
|
for (uint32 IndexIdx = Section.FirstIndex; IndexIdx < Section.FirstIndex + (Section.NumTriangles * 3); IndexIdx++)
|
|
{
|
|
CollidingIndices.Add(AllIndices[IndexIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do not perform any action if we have invalid input
|
|
if (Verts.Num() < 3 || CollidingIndices.Num() < 3)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get the BodySetup we are going to put the collision into
|
|
UBodySetup* BodySetup = StaticMesh->GetBodySetup();
|
|
if (BodySetup)
|
|
{
|
|
BodySetup->RemoveSimpleCollision();
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, create one here.
|
|
StaticMesh->CreateBodySetup();
|
|
BodySetup = StaticMesh->GetBodySetup();
|
|
}
|
|
|
|
// Run actual util to do the work (if we have some valid input)
|
|
DecomposeMeshToHulls(BodySetup, Verts, CollidingIndices, HullCount, MaxHullVerts, HullPrecision);
|
|
|
|
StaticMesh->bCustomizedCollision = true; //mark the static mesh for collision customization
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsUVChannelValid(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex)
|
|
{
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs() || LODIndex < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("The StaticMesh doesn't have LOD %d."), LODIndex);
|
|
return false;
|
|
}
|
|
|
|
if (!StaticMesh->IsMeshDescriptionValid(LODIndex))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("No mesh description for LOD %d."), LODIndex);
|
|
return false;
|
|
}
|
|
|
|
int32 NumUVChannels = StaticMesh->GetNumUVChannels(LODIndex);
|
|
if (UVChannelIndex < 0 || UVChannelIndex >= NumUVChannels)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("The given UV channel index %d is out of bounds."), UVChannelIndex);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template<typename ArrayType>
|
|
int32 ReplaceMeshes(const ArrayType& Array, UStaticMesh* MeshToBeReplaced, UStaticMesh* NewMesh)
|
|
{
|
|
//Would use FObjectEditorUtils::SetPropertyValue, but meshes are a special case. They need a lock and we need to use the SetMesh function
|
|
FProperty* StaticMeshProperty = FindFieldChecked<FProperty>(UStaticMeshComponent::StaticClass(), "StaticMesh");
|
|
TArray<UObject*, TInlineAllocator<16>> ObjectsThatChanged;
|
|
int32 NumberOfChanges = 0;
|
|
|
|
for (UStaticMeshComponent* Component : Array)
|
|
{
|
|
const bool bIsClassDefaultObject = Component->HasAnyFlags(RF_ClassDefaultObject);
|
|
if (!bIsClassDefaultObject)
|
|
{
|
|
if (Component->GetStaticMesh() == MeshToBeReplaced)
|
|
{
|
|
FEditPropertyChain PropertyChain;
|
|
PropertyChain.AddHead(StaticMeshProperty);
|
|
static_cast<UObject*>(Component)->PreEditChange(PropertyChain);
|
|
|
|
// Set the mesh
|
|
Component->SetStaticMesh(NewMesh);
|
|
++NumberOfChanges;
|
|
|
|
ObjectsThatChanged.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Route post edit change after all components have had their values changed. This is to avoid
|
|
// construction scripts from re-running in the middle of setting values and wiping out components we need to modify
|
|
for (UObject* ObjectData : ObjectsThatChanged)
|
|
{
|
|
FPropertyChangedEvent PropertyEvent(StaticMeshProperty);
|
|
ObjectData->PostEditChangeProperty(PropertyEvent);
|
|
}
|
|
|
|
return NumberOfChanges;
|
|
}
|
|
|
|
template<typename ArrayType>
|
|
int32 ReplaceMaterials(ArrayType& Array, UMaterialInterface* MaterialToBeReplaced, UMaterialInterface* NewMaterial)
|
|
{
|
|
//Would use FObjectEditorUtils::SetPropertyValue, but Material are a special case. They need a lock and we need to use the SetMaterial function
|
|
FProperty* MaterialProperty = FindFieldChecked<FProperty>(UMeshComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UMeshComponent, OverrideMaterials));
|
|
TArray<UObject*, TInlineAllocator<16>> ObjectsThatChanged;
|
|
int32 NumberOfChanges = 0;
|
|
|
|
for (UMeshComponent* Component : Array)
|
|
{
|
|
const bool bIsClassDefaultObject = Component->HasAnyFlags(RF_ClassDefaultObject);
|
|
if (!bIsClassDefaultObject)
|
|
{
|
|
const int32 NumberOfMaterial = Component->GetNumMaterials();
|
|
for (int32 Index = 0; Index < NumberOfMaterial; ++Index)
|
|
{
|
|
if (Component->GetMaterial(Index) == MaterialToBeReplaced)
|
|
{
|
|
FEditPropertyChain PropertyChain;
|
|
PropertyChain.AddHead(MaterialProperty);
|
|
static_cast<UObject*>(Component)->PreEditChange(PropertyChain);
|
|
|
|
// Set the material
|
|
Component->SetMaterial(Index, NewMaterial);
|
|
++NumberOfChanges;
|
|
|
|
ObjectsThatChanged.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Route post edit change after all components have had their values changed. This is to avoid
|
|
// construction scripts from re-running in the middle of setting values and wiping out components we need to modify
|
|
for (UObject* ObjectData : ObjectsThatChanged)
|
|
{
|
|
FPropertyChangedEvent PropertyEvent(MaterialProperty);
|
|
ObjectData->PostEditChangeProperty(PropertyEvent);
|
|
}
|
|
|
|
return NumberOfChanges;
|
|
}
|
|
|
|
template<class TPrimitiveComponent>
|
|
bool FindValidActorAndComponents(TArray<AStaticMeshActor*> ActorsToTest, TArray<AStaticMeshActor*>& OutValidActor, TArray<TPrimitiveComponent*>& OutPrimitiveComponent, FVector& OutAverageLocation, FString& OutFailureReason)
|
|
{
|
|
for (int32 Index = ActorsToTest.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
if (!IsValid(ActorsToTest[Index]))
|
|
{
|
|
ActorsToTest.RemoveAtSwap(Index);
|
|
}
|
|
}
|
|
|
|
if (ActorsToTest.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// All actors need to come from the same World
|
|
UWorld* CurrentWorld = ActorsToTest[0]->GetWorld();
|
|
if (CurrentWorld == nullptr)
|
|
{
|
|
OutFailureReason = TEXT("The actors were not in a valid world.");
|
|
return false;
|
|
}
|
|
if (CurrentWorld->WorldType != EWorldType::Editor && CurrentWorld->WorldType != EWorldType::EditorPreview)
|
|
{
|
|
OutFailureReason = TEXT("The actors were not in an editor world.");
|
|
return false;
|
|
}
|
|
|
|
ULevel* CurrentLevel = ActorsToTest[0]->GetLevel();
|
|
if (CurrentLevel == nullptr)
|
|
{
|
|
OutFailureReason = TEXT("The actors were not in a valid level.");
|
|
return false;
|
|
}
|
|
|
|
FVector PivotLocation = FVector::ZeroVector;
|
|
|
|
OutPrimitiveComponent.Reset(ActorsToTest.Num());
|
|
OutValidActor.Reset(ActorsToTest.Num());
|
|
{
|
|
bool bShowedDifferentLevelMessage = false;
|
|
for (AStaticMeshActor* MeshActor : ActorsToTest)
|
|
{
|
|
if (MeshActor->GetWorld() != CurrentWorld)
|
|
{
|
|
OutFailureReason = TEXT("Some actors were not from the same world.");
|
|
return false;
|
|
}
|
|
|
|
if (!bShowedDifferentLevelMessage && MeshActor->GetLevel() != CurrentLevel)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("Not all actors are from the same level. The Actor will be created in the first level found."));
|
|
bShowedDifferentLevelMessage = true;
|
|
}
|
|
|
|
PivotLocation += MeshActor->GetActorLocation();
|
|
|
|
TInlineComponentArray<UStaticMeshComponent*> ComponentArray;
|
|
MeshActor->GetComponents(ComponentArray);
|
|
|
|
bool bActorIsValid = false;
|
|
for (UStaticMeshComponent* MeshCmp : ComponentArray)
|
|
{
|
|
if (MeshCmp->GetStaticMesh() && MeshCmp->GetStaticMesh()->GetRenderData())
|
|
{
|
|
bActorIsValid = true;
|
|
OutPrimitiveComponent.Add(MeshCmp);
|
|
}
|
|
}
|
|
|
|
//Actor needs at least one StaticMeshComponent to be considered valid
|
|
if (bActorIsValid)
|
|
{
|
|
OutValidActor.Add(MeshActor);
|
|
}
|
|
}
|
|
}
|
|
|
|
OutAverageLocation = PivotLocation / OutValidActor.Num();
|
|
|
|
return true;
|
|
}
|
|
|
|
FName GenerateValidOwnerBasedComponentNameForNewOwner(UStaticMeshComponent* OriginalComponent, AActor* NewOwner)
|
|
{
|
|
check(OriginalComponent);
|
|
check(OriginalComponent->GetOwner());
|
|
check(NewOwner);
|
|
|
|
//Find first valid name on new owner by incrementing internal index
|
|
FName NewName = OriginalComponent->GetOwner()->GetFName();
|
|
const int32 InitialNumber = NewName.GetNumber();
|
|
while (FindObjectFast<UObject>(NewOwner, NewName) != nullptr)
|
|
{
|
|
uint32 NextNumber = NewName.GetNumber();
|
|
if (NextNumber >= 0xfffffe)
|
|
{
|
|
NewName = NAME_None;
|
|
break;
|
|
}
|
|
++NextNumber;
|
|
NewName.SetNumber(NextNumber);
|
|
}
|
|
|
|
return NewName;
|
|
}
|
|
}
|
|
|
|
UStaticMeshEditorSubsystem::UStaticMeshEditorSubsystem()
|
|
: UEditorSubsystem()
|
|
{
|
|
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::SetLodsWithNotification(UStaticMesh* StaticMesh, const FStaticMeshReductionOptions& ReductionOptions, bool bApplyChanges)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODs: The StaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
// If LOD 0 does not exist, warn and return
|
|
if (StaticMesh->GetNumSourceModels() == 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODs: This StaticMesh does not have LOD 0."));
|
|
return -1;
|
|
}
|
|
|
|
if (ReductionOptions.ReductionSettings.Num() == 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODs: Nothing done as no LOD settings were provided."));
|
|
return -1;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. If changes are applied, reopen it after the mesh has been built.
|
|
bool bStaticMeshIsEdited = false;
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
StaticMesh->Modify();
|
|
}
|
|
|
|
// Resize array of LODs to only keep LOD 0
|
|
StaticMesh->SetNumSourceModels(1);
|
|
|
|
// Set up LOD 0
|
|
StaticMesh->GetSourceModel(0).ReductionSettings.PercentTriangles = ReductionOptions.ReductionSettings[0].PercentTriangles;
|
|
StaticMesh->GetSourceModel(0).ScreenSize = ReductionOptions.ReductionSettings[0].ScreenSize;
|
|
|
|
int32 LODIndex = 1;
|
|
for (; LODIndex < ReductionOptions.ReductionSettings.Num(); ++LODIndex)
|
|
{
|
|
// Create new SourceModel for new LOD
|
|
FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
|
|
|
|
// Copy settings from previous LOD
|
|
SrcModel.BuildSettings = StaticMesh->GetSourceModel(LODIndex - 1).BuildSettings;
|
|
SrcModel.ReductionSettings = StaticMesh->GetSourceModel(LODIndex - 1).ReductionSettings;
|
|
|
|
// Modify reduction settings based on user's requirements
|
|
SrcModel.ReductionSettings.PercentTriangles = ReductionOptions.ReductionSettings[LODIndex].PercentTriangles;
|
|
SrcModel.ScreenSize = ReductionOptions.ReductionSettings[LODIndex].ScreenSize;
|
|
|
|
// Stop when reaching maximum of supported LODs
|
|
if (StaticMesh->GetNumSourceModels() == MAX_STATIC_MESH_LODS)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
StaticMesh->bAutoComputeLODScreenSize = ReductionOptions.bAutoComputeLODScreenSize ? 1 : 0;
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
// Request re-building of mesh with new LODs
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
|
|
return LODIndex;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::GetLodReductionSettings(const UStaticMesh* StaticMesh, const int32 LodIndex, FMeshReductionSettings& OutReductionOptions)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLodReductionSettings: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
// If LOD 0 does not exist, warn and return
|
|
if (LodIndex < 0 || StaticMesh->GetNumSourceModels() <= LodIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLodReductionSettings: Invalid LOD index."));
|
|
return;
|
|
}
|
|
|
|
const FStaticMeshSourceModel& LODModel = StaticMesh->GetSourceModel(LodIndex);
|
|
|
|
// Copy over the reduction settings
|
|
OutReductionOptions = LODModel.ReductionSettings;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::SetLodReductionSettings(UStaticMesh* StaticMesh, const int32 LodIndex, const FMeshReductionSettings& ReductionOptions)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodReductionSettings: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
// If LOD 0 does not exist, warn and return
|
|
if (LodIndex < 0 || StaticMesh->GetNumSourceModels() <= LodIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodReductionSettings: Invalid LOD index."));
|
|
return;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. If changes are applied, reopen it after the mesh has been built.
|
|
bool bStaticMeshIsEdited = false;
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
StaticMesh->Modify();
|
|
|
|
FStaticMeshSourceModel& LODModel = StaticMesh->GetSourceModel(LodIndex);
|
|
|
|
// Copy over the reduction settings
|
|
LODModel.ReductionSettings = ReductionOptions;
|
|
|
|
// Request re-building of mesh with new LODs
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::GetLodBuildSettings(const UStaticMesh* StaticMesh, const int32 LodIndex, FMeshBuildSettings& OutBuildOptions)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLodBuildSettings: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
// If LOD 0 does not exist, warn and return
|
|
if (LodIndex < 0 || StaticMesh->GetNumSourceModels() <= LodIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLodBuildSettings: Invalid LOD index."));
|
|
return;
|
|
}
|
|
|
|
const FStaticMeshSourceModel& LODModel = StaticMesh->GetSourceModel(LodIndex);
|
|
|
|
// Copy over the reduction settings
|
|
OutBuildOptions = LODModel.BuildSettings;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::SetLodBuildSettings(UStaticMesh* StaticMesh, const int32 LodIndex, const FMeshBuildSettings& BuildOptions)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodBuildSettings: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
// If LOD 0 does not exist, warn and return
|
|
if (LodIndex < 0 || StaticMesh->GetNumSourceModels() <= LodIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodBuildSettings: Invalid LOD index."));
|
|
return;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. If changes are applied, reopen it after the mesh has been built.
|
|
bool bStaticMeshIsEdited = false;
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
StaticMesh->Modify();
|
|
|
|
FStaticMeshSourceModel& LODModel = StaticMesh->GetSourceModel(LodIndex);
|
|
|
|
// Copy over the build settings
|
|
LODModel.BuildSettings = BuildOptions;
|
|
|
|
// Request re-building of mesh with new LODs
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
|
|
FName UStaticMeshEditorSubsystem::GetLODGroup(const UStaticMesh* StaticMesh)
|
|
{
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh GetLODGroup: The StaticMesh is null."));
|
|
return NAME_None;
|
|
}
|
|
|
|
return StaticMesh->LODGroup;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::SetLODGroup(UStaticMesh* StaticMesh, FName LODGroup, bool bRebuildImmediately)
|
|
{
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh SetLODGroup: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
TArray<FName> LODGroups;
|
|
UStaticMesh::GetLODGroups(LODGroups);
|
|
|
|
if(LODGroups.Contains(LODGroup))
|
|
{
|
|
GWarn->BeginSlowTask(FText::Format(FText::FromString("SetLODGroup: Applying changes to %s"), FText::FromString(StaticMesh->GetName())), true, false);
|
|
// Close the mesh editor to prevent crashing. If changes are applied, reopen it after the mesh has been built.
|
|
|
|
bool bStaticMeshIsEdited = false;
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
StaticMesh->SetLODGroup(LODGroup, bRebuildImmediately);
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
return true;
|
|
}
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODGroup: %s is not a valid LODGroup"), *LODGroup.ToString());
|
|
return false;
|
|
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::ImportLOD(UStaticMesh* BaseStaticMesh, const int32 LODIndex, const FString& SourceFilename)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ImportLOD: Cannot import or re-import when editor PIE is active."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
if (BaseStaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ImportLOD: The StaticMesh is null."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
// Make sure the LODIndex we want to add the LOD is valid
|
|
if (BaseStaticMesh->GetNumSourceModels() < LODIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ImportLOD: Invalid LODIndex, the LOD index cannot be greater the the number of LOD, static mesh cannot have hole in the LOD array."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
FString ResolveFilename = SourceFilename;
|
|
const bool bSourceFileExists = FPaths::FileExists(ResolveFilename);
|
|
if (!bSourceFileExists)
|
|
{
|
|
if (BaseStaticMesh->IsSourceModelValid(LODIndex))
|
|
{
|
|
const FStaticMeshSourceModel& SourceModel = BaseStaticMesh->GetSourceModel(LODIndex);
|
|
ResolveFilename = SourceModel.SourceImportFilename.IsEmpty() ?
|
|
SourceModel.SourceImportFilename :
|
|
UAssetImportData::ResolveImportFilename(SourceModel.SourceImportFilename, nullptr);
|
|
}
|
|
}
|
|
|
|
if (!FPaths::FileExists(ResolveFilename))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ImportLOD: Invalid source filename."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
constexpr bool bAsyncFalse = false;
|
|
if (!FbxMeshUtils::ImportStaticMeshLOD(BaseStaticMesh, ResolveFilename, LODIndex, bAsyncFalse))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ImportLOD: Cannot import mesh LOD."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostLODImport(BaseStaticMesh, LODIndex);
|
|
|
|
return LODIndex;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::ReimportAllCustomLODs(UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ReimportAllCustomLODs: Cannot import or re-import when editor PIE is active."));
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ReimportAllCustomLODs: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
bool bResult = true;
|
|
int32 LODNumber = StaticMesh->GetNumLODs();
|
|
//Iterate the static mesh LODs, start at index 1
|
|
for (int32 LODIndex = 1; LODIndex < LODNumber; ++LODIndex)
|
|
{
|
|
const FStaticMeshSourceModel& SourceModel = StaticMesh->GetSourceModel(LODIndex);
|
|
//Skip LOD import in the same file as the base mesh, they are already re-import
|
|
if (SourceModel.bImportWithBaseMesh)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bHasBeenSimplified = !StaticMesh->IsMeshDescriptionValid(LODIndex) || StaticMesh->IsReductionActive(LODIndex);
|
|
if (bHasBeenSimplified)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ImportLOD(StaticMesh, LODIndex, SourceModel.SourceImportFilename) != LODIndex)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("StaticMesh ReimportAllCustomLODs: Cannot re-import LOD %d."), LODIndex);
|
|
bResult = false;
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::SetLodFromStaticMesh(UStaticMesh* DestinationStaticMesh, int32 DestinationLodIndex, UStaticMesh* SourceStaticMesh, int32 SourceLodIndex, bool bReuseExistingMaterialSlots)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
if (DestinationStaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodFromStaticMesh: The DestinationStaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
if (SourceStaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodFromStaticMesh: The SourceStaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
if (!SourceStaticMesh->IsSourceModelValid(SourceLodIndex))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodFromStaticMesh: SourceLodIndex is invalid."));
|
|
return -1;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(DestinationStaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(DestinationStaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
DestinationStaticMesh->Modify();
|
|
|
|
if (DestinationStaticMesh->GetNumSourceModels() < DestinationLodIndex + 1)
|
|
{
|
|
// Add one LOD
|
|
DestinationStaticMesh->AddSourceModel();
|
|
|
|
DestinationLodIndex = DestinationStaticMesh->GetNumSourceModels() - 1;
|
|
|
|
// The newly added SourceModel won't have a MeshDescription so create it explicitly
|
|
DestinationStaticMesh->CreateMeshDescription(DestinationLodIndex);
|
|
}
|
|
|
|
// Transfers the build settings and the reduction settings.
|
|
const FStaticMeshSourceModel& SourceMeshSourceModel = SourceStaticMesh->GetSourceModel(SourceLodIndex);
|
|
FStaticMeshSourceModel& DestinationMeshSourceModel = DestinationStaticMesh->GetSourceModel(DestinationLodIndex);
|
|
DestinationMeshSourceModel.BuildSettings = SourceMeshSourceModel.BuildSettings;
|
|
DestinationMeshSourceModel.ReductionSettings = SourceMeshSourceModel.ReductionSettings;
|
|
// Base the reduction on the new lod
|
|
DestinationMeshSourceModel.ReductionSettings.BaseLODModel = DestinationLodIndex;
|
|
|
|
bool bDoesSourceLodUseReduction = SourceStaticMesh->IsReductionActive(SourceLodIndex);
|
|
|
|
|
|
int32 BaseSourceLodIndex = bDoesSourceLodUseReduction ? SourceMeshSourceModel.ReductionSettings.BaseLODModel : SourceLodIndex;
|
|
bool bIsReductionSettingAproximated = false;
|
|
|
|
// Find the original mesh description for this LOD
|
|
while (!SourceStaticMesh->IsMeshDescriptionValid(BaseSourceLodIndex))
|
|
{
|
|
if (!SourceStaticMesh->IsSourceModelValid(BaseSourceLodIndex))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodFromStaticMesh: The SourceStaticMesh is in a invalid state."));
|
|
return -1;
|
|
}
|
|
|
|
const FMeshReductionSettings& PossibleSourceMeshReductionSetting = SourceStaticMesh->GetSourceModel(BaseSourceLodIndex).ReductionSettings;
|
|
DestinationMeshSourceModel.ReductionSettings.PercentTriangles *= PossibleSourceMeshReductionSetting.PercentTriangles;
|
|
DestinationMeshSourceModel.ReductionSettings.PercentVertices *= PossibleSourceMeshReductionSetting.PercentVertices;
|
|
BaseSourceLodIndex = SourceStaticMesh->GetSourceModel(BaseSourceLodIndex).ReductionSettings.BaseLODModel;
|
|
|
|
bIsReductionSettingAproximated = true;
|
|
}
|
|
|
|
if (bIsReductionSettingAproximated)
|
|
{
|
|
TArray<FStringFormatArg> InOrderedArguments;
|
|
InOrderedArguments.Reserve(4);
|
|
InOrderedArguments.Add(SourceStaticMesh->GetName());
|
|
InOrderedArguments.Add(SourceLodIndex);
|
|
InOrderedArguments.Add(DestinationLodIndex);
|
|
InOrderedArguments.Add(DestinationStaticMesh->GetName());
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Warning, TEXT("%s"), *FString::Format(TEXT("SetLodFromStaticMesh: The reduction settings from the SourceStaticMesh {0} LOD {1} were approximated."
|
|
" The LOD {2} from {3} might not be identical."), InOrderedArguments));
|
|
}
|
|
|
|
// Copy the source import file.
|
|
DestinationMeshSourceModel.SourceImportFilename = SourceStaticMesh->GetSourceModel(BaseSourceLodIndex).SourceImportFilename;
|
|
|
|
// Copy the mesh description
|
|
const FMeshDescription& SourceMeshDescription = *SourceStaticMesh->GetMeshDescription(BaseSourceLodIndex);
|
|
FMeshDescription& DestinationMeshDescription = *DestinationStaticMesh->GetMeshDescription(DestinationLodIndex);
|
|
DestinationMeshDescription = SourceMeshDescription;
|
|
DestinationStaticMesh->CommitMeshDescription(DestinationLodIndex);
|
|
|
|
// Assign materials for the destination LOD
|
|
{
|
|
auto FindMaterialIndex = [](UStaticMesh* StaticMesh, const UMaterialInterface* Material) -> int32
|
|
{
|
|
for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++MaterialIndex)
|
|
{
|
|
if (StaticMesh->GetMaterial(MaterialIndex) == Material)
|
|
{
|
|
return MaterialIndex;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
};
|
|
|
|
TMap< int32, int32 > LodSectionMaterialMapping; // LOD section index -> destination material index
|
|
|
|
int32 NumDestinationMaterial = DestinationStaticMesh->GetStaticMaterials().Num();
|
|
|
|
const int32 SourceLodNumSections = SourceStaticMesh->GetSectionInfoMap().GetSectionNumber(SourceLodIndex);
|
|
|
|
for (int32 SourceLodSectionIndex = 0; SourceLodSectionIndex < SourceLodNumSections; ++SourceLodSectionIndex)
|
|
{
|
|
const FMeshSectionInfo& SourceMeshSectionInfo = SourceStaticMesh->GetSectionInfoMap().Get(SourceLodIndex, SourceLodSectionIndex);
|
|
|
|
const UMaterialInterface* SourceMaterial = SourceStaticMesh->GetMaterial(SourceMeshSectionInfo.MaterialIndex);
|
|
|
|
int32 DestinationMaterialIndex = INDEX_NONE;
|
|
|
|
if (bReuseExistingMaterialSlots)
|
|
{
|
|
DestinationMaterialIndex = FindMaterialIndex(DestinationStaticMesh, SourceMaterial);
|
|
}
|
|
|
|
if (DestinationMaterialIndex == INDEX_NONE)
|
|
{
|
|
DestinationMaterialIndex = NumDestinationMaterial++;
|
|
}
|
|
|
|
LodSectionMaterialMapping.Add(SourceLodSectionIndex, DestinationMaterialIndex);
|
|
}
|
|
|
|
for (TMap< int32, int32 >::TConstIterator It = LodSectionMaterialMapping.CreateConstIterator(); It; ++It)
|
|
{
|
|
const int32 SectionIndex = It->Key;
|
|
|
|
const FMeshSectionInfo& SourceSectionInfo = SourceStaticMesh->GetSectionInfoMap().Get(SourceLodIndex, SectionIndex);
|
|
|
|
UMaterialInterface* SourceMaterial = SourceStaticMesh->GetMaterial(SourceSectionInfo.MaterialIndex);
|
|
|
|
const int32 SourceMaterialIndex = SourceSectionInfo.MaterialIndex;
|
|
const int32 DestinationMaterialIndex = It->Value;
|
|
|
|
if (!DestinationStaticMesh->GetStaticMaterials().IsValidIndex(DestinationMaterialIndex))
|
|
{
|
|
DestinationStaticMesh->GetStaticMaterials().Add(SourceStaticMesh->GetStaticMaterials()[SourceSectionInfo.MaterialIndex]);
|
|
|
|
ensure(DestinationStaticMesh->GetStaticMaterials().Num() == DestinationMaterialIndex + 1); // We assume that we are not creating holes in StaticMaterials
|
|
}
|
|
|
|
FMeshSectionInfo DestinationSectionInfo = SourceSectionInfo;
|
|
DestinationSectionInfo.MaterialIndex = DestinationMaterialIndex;
|
|
|
|
DestinationStaticMesh->GetSectionInfoMap().Set(DestinationLodIndex, SectionIndex, MoveTemp(DestinationSectionInfo));
|
|
}
|
|
}
|
|
|
|
DestinationStaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(DestinationStaticMesh);
|
|
}
|
|
|
|
return DestinationLodIndex;
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetLodCount(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLODCount: The StaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
return StaticMesh->GetNumSourceModels();
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::RemoveLods(UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveLODs: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// No main LOD, skip
|
|
if (StaticMesh->GetNumSourceModels() == 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveLODs: This StaticMesh does not have LOD 0."));
|
|
return false;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
// Reduce array of source models to 1
|
|
StaticMesh->Modify();
|
|
StaticMesh->SetNumSourceModels(1);
|
|
|
|
// Request re-building of mesh with new LODs
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TArray<float> UStaticMeshEditorSubsystem::GetLodScreenSizes(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
TArray<float> ScreenSizes;
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return ScreenSizes;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetLodScreenSizes: The StaticMesh is null."));
|
|
return ScreenSizes;
|
|
}
|
|
|
|
for (int i = 0; i < StaticMesh->GetNumLODs(); i++)
|
|
{
|
|
if (StaticMesh->GetRenderData())
|
|
{
|
|
float CurScreenSize = StaticMesh->GetRenderData()->ScreenSize[i].GetValue();
|
|
ScreenSizes.Add(CurScreenSize);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Warning, TEXT("GetLodScreenSizes: The RenderData is invalid for LOD %d."), i);
|
|
}
|
|
}
|
|
|
|
return ScreenSizes;
|
|
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::SetLodScreenSizes(UStaticMesh* StaticMesh, const TArray<float>& ScreenSizes)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodScreenSizes: Input StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
|
|
if (RenderData == nullptr || (StaticMesh->GetNumLODs() == 0))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodScreenSizes: Input StaticMesh is invalid (missing RenderData or meshes)."));
|
|
return false;
|
|
}
|
|
|
|
if (ScreenSizes.Num() == 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLodScreenSizes: Input ScreenSizes array is empty."));
|
|
return false;
|
|
}
|
|
|
|
// If not enough screen sizes, we set remainder to arbitrary monotonically decreasing defaults, and also ensure consecutive
|
|
// values are monotonically decreasing, similar to what the user interface does when editing the values manually.
|
|
if (ScreenSizes.Num() < StaticMesh->GetNumLODs())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Warning, TEXT("SetLodScreenSizes: Only %d of %d ScreenSizes provided, remainder will be set to arbitrary defaults."),
|
|
ScreenSizes.Num(), StaticMesh->GetNumLODs());
|
|
}
|
|
|
|
const float MonotonicDifference = 0.0001f; // This difference value matches the user interface
|
|
bool bSanitizationRequired = false;
|
|
|
|
// Disable automatic screen size calculation, since we're providing manually overriden values
|
|
StaticMesh->bAutoComputeLODScreenSize = 0;
|
|
|
|
// Arbitrarily set this to a value that won't affect the monotonic clamping on the first iteration of the loop
|
|
float LastScreenSize = ScreenSizes[0] + 2.0f * MonotonicDifference;
|
|
|
|
for (int i = 0; i < StaticMesh->GetNumLODs(); i++)
|
|
{
|
|
float ScreenSizeForLOD;
|
|
if (i < ScreenSizes.Num())
|
|
{
|
|
ScreenSizeForLOD = FMath::Min(ScreenSizes[i], LastScreenSize - MonotonicDifference);
|
|
}
|
|
else
|
|
{
|
|
ScreenSizeForLOD = LastScreenSize - MonotonicDifference;
|
|
}
|
|
|
|
ScreenSizeForLOD = FMath::Max(ScreenSizeForLOD, 0.0f);
|
|
|
|
// Track if the input values needed to be sanitized in any way, so we can warn the user this happened.
|
|
if (i < ScreenSizes.Num() && ScreenSizeForLOD != ScreenSizes[i])
|
|
{
|
|
bSanitizationRequired = true;
|
|
}
|
|
|
|
RenderData->ScreenSize[i].Default = ScreenSizeForLOD;
|
|
StaticMesh->GetSourceModel(i).ScreenSize = ScreenSizeForLOD;
|
|
|
|
LastScreenSize = ScreenSizeForLOD;
|
|
}
|
|
|
|
if (bSanitizationRequired)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Warning, TEXT("SetLodScreenSizes: Some input values were sanitized to be monotonic."));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::ImportNaniteHiResMesh(UStaticMesh* StaticMesh, const FString& SourceFilename, bool bShowDialogWhenFileMissing)
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("ImportNaniteHiResMesh: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
FStaticMeshSourceModel& HiResModel = StaticMesh->GetHiResSourceModel();
|
|
FString ResolveFilename = SourceFilename;
|
|
if (ResolveFilename.IsEmpty())
|
|
{
|
|
//Grab the existing source
|
|
ResolveFilename = HiResModel.SourceImportFilename;
|
|
}
|
|
|
|
bool bSourceFileExists = FPaths::FileExists(ResolveFilename);
|
|
if (!bSourceFileExists)
|
|
{
|
|
ResolveFilename = ResolveFilename.IsEmpty() ?
|
|
ResolveFilename :
|
|
UAssetImportData::ResolveImportFilename(ResolveFilename, nullptr);
|
|
}
|
|
|
|
bSourceFileExists = FPaths::FileExists(ResolveFilename);
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
const bool OriginalEnableNaniteValue = StaticMesh->NaniteSettings.bEnabled;
|
|
StaticMesh->NaniteSettings.bEnabled = true;
|
|
bool bResult = true;
|
|
constexpr bool bAsyncFalse = false;
|
|
if(bSourceFileExists)
|
|
{
|
|
|
|
if (!FbxMeshUtils::ImportStaticMeshHiResSourceModel(StaticMesh, ResolveFilename, bAsyncFalse))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("ImportNaniteHiResMesh: Cannot import hi res mesh."));
|
|
bResult = false;
|
|
}
|
|
}
|
|
else if (bShowDialogWhenFileMissing)
|
|
{
|
|
if (!FbxMeshUtils::ImportStaticMeshHiResSourceModelDialog(StaticMesh))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("ImportNaniteHiResMesh: Cannot import hi res mesh with dialog."));
|
|
bResult = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("ImportNaniteHiResMesh: Cannot import hi res mesh, the provided file is invalid."));
|
|
bResult = false;
|
|
}
|
|
|
|
if (!bResult)
|
|
{
|
|
StaticMesh->NaniteSettings.bEnabled = OriginalEnableNaniteValue;
|
|
}
|
|
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
FString UStaticMeshEditorSubsystem::GetNaniteSourceFilename(const UStaticMesh* StaticMesh)
|
|
{
|
|
FString ResolveFilename;
|
|
if (!StaticMesh)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetNaniteSourceFilename: The StaticMesh is null."));
|
|
return ResolveFilename;
|
|
}
|
|
const FStaticMeshSourceModel& HiResModel = StaticMesh->GetHiResSourceModel();
|
|
ResolveFilename = HiResModel.SourceImportFilename;
|
|
bool bSourceFileExists = FPaths::FileExists(ResolveFilename);
|
|
if (!bSourceFileExists)
|
|
{
|
|
ResolveFilename = ResolveFilename.IsEmpty() ?
|
|
ResolveFilename :
|
|
UAssetImportData::ResolveImportFilename(ResolveFilename, nullptr);
|
|
}
|
|
return ResolveFilename;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::UpdateNaniteSourceFilename(UStaticMesh* StaticMesh, const FString& NewSourceFilename)
|
|
{
|
|
bool bResult = false;
|
|
FString ResolveFilename = NewSourceFilename;
|
|
if (!StaticMesh)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("UpdateNaniteSourceFilename: The StaticMesh is null."));
|
|
return bResult;
|
|
}
|
|
if (!FPaths::FileExists(ResolveFilename))
|
|
{
|
|
ResolveFilename = ResolveFilename.IsEmpty() ?
|
|
ResolveFilename :
|
|
UAssetImportData::ResolveImportFilename(ResolveFilename, nullptr);
|
|
}
|
|
if (!StaticMesh->GetHiResSourceModel().SourceImportFilename.Equals(ResolveFilename))
|
|
{
|
|
if (ResolveFilename.IsEmpty())
|
|
{
|
|
FbxMeshUtils::RemoveStaticMeshHiRes(StaticMesh);
|
|
bResult = true;
|
|
}
|
|
else if(FPaths::FileExists(ResolveFilename))
|
|
{
|
|
//We have a valid path, update it and re-import the nanite mesh
|
|
StaticMesh->GetHiResSourceModel().SourceImportFilename = ResolveFilename;
|
|
ImportNaniteHiResMesh(StaticMesh, ResolveFilename, false);
|
|
bResult = true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("UpdateNaniteSourceFilename: The new source filename is invalid (it can be empty or must point to an existing filename)."));
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
FMeshNaniteSettings UStaticMeshEditorSubsystem::GetNaniteSettings(const UStaticMesh* StaticMesh)
|
|
{
|
|
if (StaticMesh)
|
|
{
|
|
return StaticMesh->NaniteSettings;
|
|
}
|
|
else
|
|
{
|
|
FFrame::KismetExecutionMessage(TEXT("Cannot call GetNaniteSettings without a static mesh"), ELogVerbosity::Error);
|
|
return FMeshNaniteSettings();
|
|
}
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::SetNaniteSettings(UStaticMesh* StaticMesh, FMeshNaniteSettings NaniteSettings, bool bApplyChanges)
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
FFrame::KismetExecutionMessage(TEXT("Cannot call SetNaniteSettings without a static mesh"), ELogVerbosity::Error);
|
|
return;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
StaticMesh->Modify();
|
|
StaticMesh->NaniteSettings = NaniteSettings;
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
// Request re-building of mesh with new collision shapes
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::AddSimpleCollisionsWithNotification(UStaticMesh* StaticMesh, const EScriptCollisionShapeType ShapeType, bool bApplyChanges)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("AddSimpleCollisions: The StaticMesh is null."));
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
int32 PrimIndex = INDEX_NONE;
|
|
|
|
switch (ShapeType)
|
|
{
|
|
case EScriptCollisionShapeType::Box:
|
|
{
|
|
PrimIndex = GenerateBoxAsSimpleCollision(StaticMesh);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.BoxElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::Sphere:
|
|
{
|
|
PrimIndex = GenerateSphereAsSimpleCollision(StaticMesh);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.SphereElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::Capsule:
|
|
{
|
|
PrimIndex = GenerateSphylAsSimpleCollision(StaticMesh);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.SphylElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::NDOP10_X:
|
|
{
|
|
TArray<FVector> DirArray(KDopDir10X, 10);
|
|
PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.ConvexElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::NDOP10_Y:
|
|
{
|
|
TArray<FVector> DirArray(KDopDir10Y, 10);
|
|
PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.ConvexElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::NDOP10_Z:
|
|
{
|
|
TArray<FVector> DirArray(KDopDir10Z, 10);
|
|
PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.ConvexElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::NDOP18:
|
|
{
|
|
TArray<FVector> DirArray(KDopDir18, 18);
|
|
PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.ConvexElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptCollisionShapeType::NDOP26:
|
|
{
|
|
TArray<FVector> DirArray(KDopDir26, 26);
|
|
PrimIndex = GenerateKDopAsSimpleCollision(StaticMesh, DirArray);
|
|
if(PrimIndex != INDEX_NONE)
|
|
{
|
|
StaticMesh->GetBodySetup()->AggGeom.ConvexElems[PrimIndex].bIsGenerated = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
// Request re-building of mesh with new collision shapes
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
|
|
return PrimIndex;
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetSimpleCollisionCount(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetSimpleCollisionCount: The StaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
UBodySetup* BodySetup = StaticMesh->GetBodySetup();
|
|
if (BodySetup == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int32 Count = BodySetup->AggGeom.BoxElems.Num();
|
|
Count += BodySetup->AggGeom.SphereElems.Num();
|
|
Count += BodySetup->AggGeom.SphylElems.Num();
|
|
|
|
return Count;
|
|
}
|
|
|
|
TEnumAsByte<ECollisionTraceFlag> UStaticMeshEditorSubsystem::GetCollisionComplexity(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetCollisionComplexity: The StaticMesh is null."));
|
|
return ECollisionTraceFlag::CTF_UseDefault;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return ECollisionTraceFlag::CTF_UseDefault;
|
|
}
|
|
|
|
if (StaticMesh->GetBodySetup())
|
|
{
|
|
return StaticMesh->GetBodySetup()->CollisionTraceFlag;
|
|
}
|
|
|
|
return ECollisionTraceFlag::CTF_UseDefault;
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetConvexCollisionCount(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetConvexCollisionCount: The StaticMesh is null."));
|
|
return -1;
|
|
}
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
UBodySetup* BodySetup = StaticMesh->GetBodySetup();
|
|
if (BodySetup == nullptr)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return BodySetup->AggGeom.ConvexElems.Num();
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::BulkSetConvexDecompositionCollisionsWithNotification(const TArray<UStaticMesh*>& InStaticMeshes, int32 HullCount, int32 MaxHullVerts, int32 HullPrecision, bool bApplyChanges)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UStaticMeshEditorSubsystem::SetConvexDecompositionCollisionsWithNotification)
|
|
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<UStaticMesh*> StaticMeshes(InStaticMeshes);
|
|
StaticMeshes.RemoveAll([](const UStaticMesh* StaticMesh) { return StaticMesh == nullptr || !StaticMesh->IsMeshDescriptionValid(0); });
|
|
|
|
if (StaticMeshes.Num() == 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetConvexDecompositionCollisions: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (HullCount < 0 || HullPrecision < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetConvexDecompositionCollisions: Parameters HullCount and HullPrecision must be positive."));
|
|
return false;
|
|
}
|
|
|
|
if (Algo::AnyOf(StaticMeshes, [](const UStaticMesh* StaticMesh) { return StaticMesh->GetRenderData() == nullptr; }))
|
|
{
|
|
UStaticMesh::BatchBuild(StaticMeshes);
|
|
}
|
|
|
|
Algo::SortBy(
|
|
StaticMeshes,
|
|
[](const UStaticMesh* StaticMesh) { return StaticMesh->GetRenderData()->LODResources[0].VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); },
|
|
TGreater<>()
|
|
);
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
|
|
TSet<UStaticMesh*> EditedStaticMeshes;
|
|
for (UStaticMesh* StaticMesh : StaticMeshes)
|
|
{
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
EditedStaticMeshes.Add(StaticMesh);
|
|
}
|
|
|
|
if (StaticMesh->GetBodySetup())
|
|
{
|
|
if (bApplyChanges)
|
|
{
|
|
StaticMesh->GetBodySetup()->Modify();
|
|
}
|
|
|
|
// Remove simple collisions
|
|
StaticMesh->GetBodySetup()->RemoveSimpleCollision();
|
|
}
|
|
}
|
|
|
|
TArray<bool> bResults;
|
|
bResults.SetNumZeroed(StaticMeshes.Num());
|
|
|
|
TAtomic<uint32> Processed(0);
|
|
TFuture<void> Result =
|
|
Async(
|
|
EAsyncExecution::ThreadPool,
|
|
[&Processed, &bResults, &StaticMeshes, HullCount, MaxHullVerts, HullPrecision]()
|
|
{
|
|
ParallelFor(
|
|
StaticMeshes.Num(),
|
|
[&Processed, &bResults, &StaticMeshes, HullCount, MaxHullVerts, HullPrecision](int32 Index)
|
|
{
|
|
bResults[Index] = InternalEditorMeshLibrary::GenerateConvexCollision(StaticMeshes[Index], HullCount, MaxHullVerts, HullPrecision);
|
|
Processed++;
|
|
},
|
|
EParallelForFlags::Unbalanced
|
|
);
|
|
}
|
|
);
|
|
|
|
uint32 LastProcessed = 0;
|
|
const FText ProgressText = LOCTEXT("ComputingConvexCollision", "Computing convex collision for static mesh {0}/{1} ...");
|
|
FScopedSlowTask Progress(static_cast<float>(StaticMeshes.Num()), FText::Format(ProgressText, LastProcessed, StaticMeshes.Num()));
|
|
Progress.MakeDialog();
|
|
|
|
while (!Result.WaitFor(FTimespan::FromMilliseconds(33.0)))
|
|
{
|
|
uint32 LocalProcessed = Processed.Load(EMemoryOrder::Relaxed);
|
|
Progress.EnterProgressFrame(static_cast<float>(LocalProcessed - LastProcessed), FText::Format(ProgressText, LocalProcessed, StaticMeshes.Num()));
|
|
LastProcessed = LocalProcessed;
|
|
}
|
|
|
|
// refresh collision change back to static mesh components
|
|
RefreshCollisionChanges(StaticMeshes);
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
for (UStaticMesh* StaticMesh : StaticMeshes)
|
|
{
|
|
// Mark mesh as dirty
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
// Request re-building of mesh following collision changes
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
for (UStaticMesh* StaticMesh : EditedStaticMeshes)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
|
|
return Algo::AllOf(bResults);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::SetConvexDecompositionCollisionsWithNotification(UStaticMesh* StaticMesh, int32 HullCount, int32 MaxHullVerts, int32 HullPrecision, bool bApplyChanges)
|
|
{
|
|
return BulkSetConvexDecompositionCollisionsWithNotification({ StaticMesh }, HullCount, MaxHullVerts, HullPrecision, bApplyChanges);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::RemoveCollisionsWithNotification(UStaticMesh* StaticMesh, bool bApplyChanges)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveCollisions: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh->GetBodySetup() == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("RemoveCollisions: No collision set up. Nothing to do."));
|
|
return true;
|
|
}
|
|
|
|
// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built.
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
bool bStaticMeshIsEdited = false;
|
|
if (AssetEditorSubsystem->FindEditorForAsset(StaticMesh, false))
|
|
{
|
|
AssetEditorSubsystem->CloseAllEditorsForAsset(StaticMesh);
|
|
bStaticMeshIsEdited = true;
|
|
}
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
StaticMesh->GetBodySetup()->Modify();
|
|
}
|
|
|
|
// Remove simple collisions
|
|
StaticMesh->GetBodySetup()->RemoveSimpleCollision();
|
|
|
|
// refresh collision change back to static mesh components
|
|
RefreshCollisionChange(*StaticMesh);
|
|
|
|
if (bApplyChanges)
|
|
{
|
|
// Request re-building of mesh with new collision shapes
|
|
StaticMesh->PostEditChange();
|
|
|
|
// Reopen MeshEditor on this mesh if the MeshEditor was previously opened in it
|
|
if (bStaticMeshIsEdited)
|
|
{
|
|
AssetEditorSubsystem->OpenEditorForAsset(StaticMesh);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::CanModifyStaticMeshSection(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex, const FString& LogFunctionName)
|
|
{
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("%s: The StaticMesh is null."), *LogFunctionName);
|
|
return false;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("%s: Invalid LOD index %d (of %d)."), *LogFunctionName, LODIndex, StaticMesh->GetNumLODs());
|
|
return false;
|
|
}
|
|
|
|
if (SectionIndex >= StaticMesh->GetNumSections(LODIndex))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("%s: Invalid section index %d (of %d)."), *LogFunctionName, SectionIndex, StaticMesh->GetNumSections(LODIndex));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::EnableSectionCastShadow(UStaticMesh* StaticMesh, bool bCastShadow, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "EnableSectionCastShadow"))
|
|
{
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.bCastShadow = bCastShadow;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::IsSectionCastShadowEnabled(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "IsSectionCastShadowEnabled"))
|
|
{
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
return SectionInfo.bCastShadow;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::EnableSectionCollision(UStaticMesh* StaticMesh, bool bCollisionEnabled, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "EnableSectionCollision"))
|
|
{
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.bEnableCollision = bCollisionEnabled;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::IsSectionCollisionEnabled(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "IsSectionCollisionEnabled"))
|
|
{
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
return SectionInfo.bEnableCollision;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::EnableSectionVisibleInRayTracing(UStaticMesh* StaticMesh, bool bVisibleInRayTracing, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "EnableSectionVisibleInRayTracing"))
|
|
{
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.bVisibleInRayTracing = bVisibleInRayTracing;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::IsSectionVisibleInRayTracingEnabled(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "IsSectionVisibleInRayTracingEnabled"))
|
|
{
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
return SectionInfo.bVisibleInRayTracing;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::EnableSectionAffectDistanceFieldLighting(UStaticMesh* StaticMesh, bool bAffectDistanceFieldLighting, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "EnableSectionAffectDistanceFieldLighting"))
|
|
{
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.bAffectDistanceFieldLighting = bAffectDistanceFieldLighting;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::IsSectionAffectDistanceFieldLightingEnabled(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "IsSectionAffectDistanceFieldLightingEnabled"))
|
|
{
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
return SectionInfo.bAffectDistanceFieldLighting;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::EnableSectionForceOpaque(UStaticMesh* StaticMesh, bool bForceOpaque, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "EnableSectionForceOpaque"))
|
|
{
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.bForceOpaque = bForceOpaque;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::IsSectionForceOpaqueEnabled(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (CanModifyStaticMeshSection(StaticMesh, LODIndex, SectionIndex, "IsSectionForceOpaqueEnabled"))
|
|
{
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
return SectionInfo.bForceOpaque;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::SetLODMaterialSlot(UStaticMesh* StaticMesh, int32 MaterialSlotIndex, int32 LODIndex, int32 SectionIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODMaterialSlot: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODMaterialSlot: Invalid LOD index %d (of %d)."), LODIndex, StaticMesh->GetNumLODs());
|
|
return;
|
|
}
|
|
|
|
if (SectionIndex >= StaticMesh->GetNumSections(LODIndex))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODMaterialSlot: Invalid section index %d (of %d)."), SectionIndex, StaticMesh->GetNumSections(LODIndex));
|
|
return;
|
|
}
|
|
|
|
if (MaterialSlotIndex >= StaticMesh->GetStaticMaterials().Num())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetLODMaterialSlot: Invalid slot index %d (of %d)."), MaterialSlotIndex, StaticMesh->GetStaticMaterials().Num());
|
|
return;
|
|
}
|
|
|
|
StaticMesh->Modify();
|
|
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
|
|
|
|
SectionInfo.MaterialIndex = MaterialSlotIndex;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
|
|
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
|
|
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetLODMaterialSlot(const UStaticMesh* StaticMesh, int32 LODIndex, int32 SectionIndex )
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard( GIsRunningUnattendedScript, true );
|
|
|
|
if ( !EditorScriptingHelpers::CheckIfInEditorAndPIE() )
|
|
{
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
if ( StaticMesh == nullptr )
|
|
{
|
|
UE_LOG( LogStaticMeshEditorSubsystem, Error, TEXT( "GetLODMaterialSlot: The StaticMesh is null." ) );
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
if ( LODIndex >= StaticMesh->GetNumLODs() )
|
|
{
|
|
UE_LOG( LogStaticMeshEditorSubsystem, Error, TEXT( "GetLODMaterialSlot: Invalid LOD index %d (of %d)." ), LODIndex, StaticMesh->GetNumLODs() );
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
if ( SectionIndex >= StaticMesh->GetNumSections( LODIndex ) )
|
|
{
|
|
UE_LOG( LogStaticMeshEditorSubsystem, Error, TEXT( "GetLODMaterialSlot: Invalid section index %d (of %d)." ), SectionIndex, StaticMesh->GetNumSections( LODIndex ) );
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
return StaticMesh->GetSectionInfoMap().Get( LODIndex, SectionIndex ).MaterialIndex;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::HasVertexColors(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("HasVertexColors: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
for (int32 LodIndex = 0; LodIndex < StaticMesh->GetNumSourceModels(); ++LodIndex)
|
|
{
|
|
const FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LodIndex);
|
|
FStaticMeshConstAttributes Attributes(*MeshDescription);
|
|
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
|
|
if (!VertexInstanceColors.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
FLinearColor VertexInstanceColor(VertexInstanceColors[VertexInstanceID]);
|
|
if (VertexInstanceColor != FLinearColor::White)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::HasInstanceVertexColors(const UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMeshComponent == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("HasInstanceVertexColors: The StaticMeshComponent is null."));
|
|
return false;
|
|
}
|
|
|
|
for (const FStaticMeshComponentLODInfo& CurrentLODInfo : StaticMeshComponent->LODData)
|
|
{
|
|
if (CurrentLODInfo.OverrideVertexColors != nullptr || CurrentLODInfo.PaintedVertices.Num() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::SetGenerateLightmapUVs(UStaticMesh* StaticMesh, bool bGenerateLightmapUVs)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetGenerateLightmapUVs: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
bool AnySettingsToChange = false;
|
|
for (int32 LodIndex = 0; LodIndex < StaticMesh->GetNumSourceModels(); ++LodIndex)
|
|
{
|
|
FStaticMeshSourceModel& SourceModel = StaticMesh->GetSourceModel(LodIndex);
|
|
//Make sure LOD is not a reduction before considering its BuildSettings
|
|
if (StaticMesh->IsMeshDescriptionValid(LodIndex))
|
|
{
|
|
AnySettingsToChange = (SourceModel.BuildSettings.bGenerateLightmapUVs != bGenerateLightmapUVs);
|
|
|
|
if (AnySettingsToChange)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AnySettingsToChange)
|
|
{
|
|
StaticMesh->Modify();
|
|
for (int32 LodIndex = 0; LodIndex < StaticMesh->GetNumSourceModels(); LodIndex++)
|
|
{
|
|
StaticMesh->GetSourceModel(LodIndex).BuildSettings.bGenerateLightmapUVs = bGenerateLightmapUVs;
|
|
}
|
|
|
|
StaticMesh->Build();
|
|
StaticMesh->PostEditChange();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetNumberVerts(const UStaticMesh* StaticMesh, int32 LODIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetNumberVerts: The StaticMesh is null."));
|
|
return 0;
|
|
}
|
|
|
|
return StaticMesh->GetNumVertices(LODIndex);
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetNumberMaterials(const UStaticMesh* StaticMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetNumberMaterials: The StaticMesh is null."));
|
|
return 0;
|
|
}
|
|
|
|
return StaticMesh->GetStaticMaterials().Num();
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::SetAllowCPUAccess(UStaticMesh* StaticMesh, bool bAllowCPUAccess)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("SetAllowCPUAccess: The StaticMesh is null."));
|
|
return;
|
|
}
|
|
|
|
StaticMesh->Modify();
|
|
StaticMesh->bAllowCPUAccess = bAllowCPUAccess;
|
|
StaticMesh->PostEditChange();
|
|
}
|
|
|
|
int32 UStaticMeshEditorSubsystem::GetNumUVChannels(const UStaticMesh* StaticMesh, int32 LODIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetNumUVChannels: The StaticMesh is null."));
|
|
return 0;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs() || LODIndex < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("GetNumUVChannels: The StaticMesh doesn't have LOD %d."), LODIndex);
|
|
return 0;
|
|
}
|
|
|
|
return StaticMesh->GetNumUVChannels(LODIndex);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::AddUVChannel(UStaticMesh* StaticMesh, int32 LODIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("AddUVChannel: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs() || LODIndex < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("AddUVChannel: The StaticMesh doesn't have LOD %d."), LODIndex);
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh->GetNumUVChannels(LODIndex) >= MAX_MESH_TEXTURE_COORDS_MD)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("AddUVChannel: Cannot add UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS_MD);
|
|
return false;
|
|
}
|
|
|
|
return StaticMesh->AddUVChannel(LODIndex);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::InsertUVChannel(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("InsertUVChannel: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs() || LODIndex < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("InsertUVChannel: The StaticMesh doesn't have LOD %d."), LODIndex);
|
|
return false;
|
|
}
|
|
|
|
int32 NumUVChannels = StaticMesh->GetNumUVChannels(LODIndex);
|
|
if (UVChannelIndex < 0 || UVChannelIndex > NumUVChannels)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("InsertUVChannel: Cannot insert UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
|
|
return false;
|
|
}
|
|
|
|
if (NumUVChannels >= MAX_MESH_TEXTURE_COORDS_MD)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("InsertUVChannel: Cannot add UV channel. Maximum number of UV channels reached (%d)."), MAX_MESH_TEXTURE_COORDS_MD);
|
|
return false;
|
|
}
|
|
|
|
return StaticMesh->InsertUVChannel(LODIndex, UVChannelIndex);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::RemoveUVChannel(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMesh == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveUVChannel: The StaticMesh is null."));
|
|
return false;
|
|
}
|
|
|
|
if (LODIndex >= StaticMesh->GetNumLODs() || LODIndex < 0)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveUVChannel: The StaticMesh doesn't have LOD %d."), LODIndex);
|
|
return false;
|
|
}
|
|
|
|
int32 NumUVChannels = StaticMesh->GetNumUVChannels(LODIndex);
|
|
if (NumUVChannels == 1)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. There must be at least one channel."));
|
|
return false;
|
|
}
|
|
|
|
if (UVChannelIndex < 0 || UVChannelIndex >= NumUVChannels)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("RemoveUVChannel: Cannot remove UV channel. Given UV channel index %d is out of bounds."), UVChannelIndex);
|
|
return false;
|
|
}
|
|
|
|
return StaticMesh->RemoveUVChannel(LODIndex, UVChannelIndex);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::GeneratePlanarUVChannel(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex, const FVector& Position, const FRotator& Orientation, const FVector2D& Tiling)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InternalEditorMeshLibrary::IsUVChannelValid(StaticMesh, LODIndex, UVChannelIndex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
|
|
|
|
FUVMapParameters UVParameters(Position, Orientation.Quaternion(), StaticMesh->GetBoundingBox().GetSize(), FVector::OneVector, Tiling);
|
|
|
|
TMap<FVertexInstanceID, FVector2D> TexCoords;
|
|
FStaticMeshOperations::GeneratePlanarUV(*MeshDescription, UVParameters, TexCoords);
|
|
|
|
return StaticMesh->SetUVChannel(LODIndex, UVChannelIndex, TexCoords);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::GenerateCylindricalUVChannel(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex, const FVector& Position, const FRotator& Orientation, const FVector2D& Tiling)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InternalEditorMeshLibrary::IsUVChannelValid(StaticMesh, LODIndex, UVChannelIndex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
|
|
|
|
FUVMapParameters UVParameters(Position, Orientation.Quaternion(), StaticMesh->GetBoundingBox().GetSize(), FVector::OneVector, Tiling);
|
|
|
|
TMap<FVertexInstanceID, FVector2D> TexCoords;
|
|
FStaticMeshOperations::GenerateCylindricalUV(*MeshDescription, UVParameters, TexCoords);
|
|
|
|
return StaticMesh->SetUVChannel(LODIndex, UVChannelIndex, TexCoords);
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::GenerateBoxUVChannel(UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannelIndex, const FVector& Position, const FRotator& Orientation, const FVector& Size)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InternalEditorMeshLibrary::IsUVChannelValid(StaticMesh, LODIndex, UVChannelIndex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
|
|
|
|
FUVMapParameters UVParameters(Position, Orientation.Quaternion(), Size, FVector::OneVector, FVector2D::UnitVector);
|
|
|
|
TMap<FVertexInstanceID, FVector2D> TexCoords;
|
|
FStaticMeshOperations::GenerateBoxUV(*MeshDescription, UVParameters, TexCoords);
|
|
|
|
return StaticMesh->SetUVChannel(LODIndex, UVChannelIndex, TexCoords);
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::ReplaceMeshComponentsMaterials(const TArray<UMeshComponent*>& MeshComponents, UMaterialInterface* MaterialToBeReplaced, UMaterialInterface* NewMaterial)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction ScopedTransaction(LOCTEXT("ReplaceMeshComponentsMaterials", "Replace components materials"));
|
|
|
|
int32 ChangeCounter = InternalEditorMeshLibrary::ReplaceMaterials(MeshComponents, MaterialToBeReplaced, NewMaterial);
|
|
|
|
if (ChangeCounter > 0)
|
|
{
|
|
// Redraw viewports to reflect the material changes
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("ReplaceMeshComponentsMaterials. %d material(s) changed occurred."), ChangeCounter);
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::ReplaceMeshComponentsMaterialsOnActors(const TArray<AActor*>& Actors, UMaterialInterface* MaterialToBeReplaced, UMaterialInterface* NewMaterial)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction ScopedTransaction(LOCTEXT("ReplaceComponentUsedMaterial", "Replace components materials"));
|
|
|
|
int32 ChangeCounter = 0;
|
|
TInlineComponentArray<UMeshComponent*> ComponentArray;
|
|
|
|
for (AActor* Actor : Actors)
|
|
{
|
|
if (Actor && IsValidChecked(Actor))
|
|
{
|
|
Actor->GetComponents(ComponentArray);
|
|
ChangeCounter += InternalEditorMeshLibrary::ReplaceMaterials(ComponentArray, MaterialToBeReplaced, NewMaterial);
|
|
}
|
|
}
|
|
|
|
if (ChangeCounter > 0)
|
|
{
|
|
// Redraw viewports to reflect the material changes
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("ReplaceMeshComponentsMaterialsOnActors. %d material(s) changed occurred."), ChangeCounter);
|
|
}
|
|
|
|
|
|
void UStaticMeshEditorSubsystem::ReplaceMeshComponentsMeshes(const TArray<UStaticMeshComponent*>& MeshComponents, UStaticMesh* MeshToBeReplaced, UStaticMesh* NewMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction ScopedTransaction(LOCTEXT("ReplaceMeshComponentsMeshes", "Replace components meshes"));
|
|
|
|
int32 ChangeCounter = InternalEditorMeshLibrary::ReplaceMeshes(MeshComponents, MeshToBeReplaced, NewMesh);
|
|
|
|
if (ChangeCounter > 0)
|
|
{
|
|
// Redraw viewports to reflect the material changes
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("ReplaceMeshComponentsMeshes. %d mesh(es) changed occurred."), ChangeCounter);
|
|
}
|
|
|
|
void UStaticMeshEditorSubsystem::ReplaceMeshComponentsMeshesOnActors(const TArray<AActor*>& Actors, UStaticMesh* MeshToBeReplaced, UStaticMesh* NewMesh)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FScopedTransaction ScopedTransaction(LOCTEXT("ReplaceMeshComponentsMeshes", "Replace components meshes"));
|
|
|
|
int32 ChangeCounter = 0;
|
|
TInlineComponentArray<UStaticMeshComponent*> ComponentArray;
|
|
|
|
for (AActor* Actor : Actors)
|
|
{
|
|
if (Actor && IsValidChecked(Actor))
|
|
{
|
|
Actor->GetComponents(ComponentArray);
|
|
ChangeCounter += InternalEditorMeshLibrary::ReplaceMeshes(ComponentArray, MeshToBeReplaced, NewMesh);
|
|
}
|
|
}
|
|
|
|
if (ChangeCounter > 0)
|
|
{
|
|
// Redraw viewports to reflect the material changes
|
|
GEditor->RedrawLevelEditingViewports();
|
|
}
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("ReplaceMeshComponentsMeshesOnActors. %d mesh(es) changed occurred."), ChangeCounter);
|
|
}
|
|
|
|
AActor* UStaticMeshEditorSubsystem::JoinStaticMeshActors(const TArray<AStaticMeshActor*>& ActorsToMerge, const FJoinStaticMeshActorsOptions& JoinOptions)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
TArray<AStaticMeshActor*> AllActors;
|
|
TArray<UStaticMeshComponent*> AllComponents;
|
|
FVector PivotLocation;
|
|
FString FailureReason;
|
|
if (!InternalEditorMeshLibrary::FindValidActorAndComponents(ActorsToMerge, AllActors, AllComponents, PivotLocation, FailureReason))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("JoinStaticMeshActors failed. %s"), *FailureReason);
|
|
return nullptr;
|
|
}
|
|
|
|
if (AllActors.Num() < 2)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("JoinStaticMeshActors failed. A merge operation requires at least 2 valid Actors."));
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the new Actor
|
|
FActorSpawnParameters Params;
|
|
Params.OverrideLevel = AllActors[0]->GetLevel();
|
|
AActor* NewActor = AllActors[0]->GetWorld()->SpawnActor<AActor>(PivotLocation, FRotator::ZeroRotator, Params);
|
|
if (!NewActor)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("JoinStaticMeshActors failed. Internal error while creating the join actor."));
|
|
return nullptr;
|
|
}
|
|
|
|
if (!JoinOptions.NewActorLabel.IsEmpty())
|
|
{
|
|
NewActor->SetActorLabel(JoinOptions.NewActorLabel);
|
|
}
|
|
|
|
// Duplicate and attach all components to the new actors
|
|
USceneComponent* NewRootComponent = NewObject<USceneComponent>(NewActor, TEXT("Root"));
|
|
NewActor->SetRootComponent(NewRootComponent);
|
|
NewRootComponent->SetMobility(EComponentMobility::Static);
|
|
for (UStaticMeshComponent* ActorCmp : AllComponents)
|
|
{
|
|
FName NewName = NAME_None;
|
|
if (JoinOptions.bRenameComponentsFromSource)
|
|
{
|
|
NewName = InternalEditorMeshLibrary::GenerateValidOwnerBasedComponentNameForNewOwner(ActorCmp, NewActor);
|
|
}
|
|
|
|
UStaticMeshComponent* NewComponent = DuplicateObject<UStaticMeshComponent>(ActorCmp, NewActor, NewName);
|
|
NewActor->AddInstanceComponent(NewComponent);
|
|
FTransform CmpTransform = ActorCmp->GetComponentToWorld();
|
|
NewComponent->SetComponentToWorld(CmpTransform);
|
|
NewComponent->AttachToComponent(NewRootComponent, FAttachmentTransformRules::KeepWorldTransform);
|
|
NewComponent->RegisterComponent();
|
|
}
|
|
|
|
if (JoinOptions.bDestroySourceActors)
|
|
{
|
|
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
UWorld* World = AllActors[0]->GetWorld();
|
|
for (AActor* Actor : AllActors)
|
|
{
|
|
Layers->DisassociateActorFromLayers(Actor);
|
|
World->EditorDestroyActor(Actor, true);
|
|
}
|
|
}
|
|
|
|
//Select newly created actor
|
|
GEditor->SelectNone(false, true, false);
|
|
GEditor->SelectActor(NewActor, true, false);
|
|
GEditor->NoteSelectionChange();
|
|
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Log, TEXT("JoinStaticMeshActors joined %d actors toghether in actor '%s'."), AllComponents.Num(), *NewActor->GetActorLabel());
|
|
return NewActor;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::MergeStaticMeshActors(const TArray<AStaticMeshActor*>& ActorsToMerge, const FMergeStaticMeshActorsOptions& MergeOptions, AStaticMeshActor*& OutMergedActor)
|
|
{
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
OutMergedActor = nullptr;
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UUnrealEditorSubsystem* UnrealEditorSubsystem = GEditor->GetEditorSubsystem<UUnrealEditorSubsystem>();
|
|
|
|
if (!UnrealEditorSubsystem)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FString FailureReason;
|
|
FString PackageName = EditorScriptingHelpers::ConvertAnyPathToLongPackagePath(MergeOptions.BasePackageName, FailureReason);
|
|
if (PackageName.IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("MergeStaticMeshActors. Failed to convert the BasePackageName. %s"), *FailureReason);
|
|
return false;
|
|
}
|
|
|
|
TArray<AStaticMeshActor*> AllActors;
|
|
TArray<UPrimitiveComponent*> AllComponents;
|
|
FVector PivotLocation;
|
|
if (!InternalEditorMeshLibrary::FindValidActorAndComponents(ActorsToMerge, AllActors, AllComponents, PivotLocation, FailureReason))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("MergeStaticMeshActors failed. %s"), *FailureReason);
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// See MeshMergingTool.cpp
|
|
//
|
|
const IMeshMergeUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
|
|
|
|
|
|
FVector MergedActorLocation;
|
|
TArray<UObject*> CreatedAssets;
|
|
const float ScreenAreaSize = TNumericLimits<float>::Max();
|
|
MeshUtilities.MergeComponentsToStaticMesh(AllComponents, AllActors[0]->GetWorld(), MergeOptions.MeshMergingSettings, nullptr, nullptr, PackageName, CreatedAssets, MergedActorLocation, ScreenAreaSize, true);
|
|
|
|
UStaticMesh* MergedMesh = nullptr;
|
|
if (!CreatedAssets.FindItemByClass(&MergedMesh))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("MergeStaticMeshActors failed. No mesh was created."));
|
|
return false;
|
|
}
|
|
|
|
FAssetRegistryModule& AssetRegistry = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
for (UObject* Obj : CreatedAssets)
|
|
{
|
|
AssetRegistry.AssetCreated(Obj);
|
|
}
|
|
|
|
//Also notify the content browser that the new assets exists
|
|
if (!IsRunningCommandlet())
|
|
{
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
ContentBrowserModule.Get().SyncBrowserToAssets(CreatedAssets, true);
|
|
}
|
|
|
|
// Place new mesh in the world
|
|
if (MergeOptions.bSpawnMergedActor)
|
|
{
|
|
FActorSpawnParameters Params;
|
|
Params.OverrideLevel = AllActors[0]->GetLevel();
|
|
OutMergedActor = AllActors[0]->GetWorld()->SpawnActor<AStaticMeshActor>(MergedActorLocation, FRotator::ZeroRotator, Params);
|
|
if (!OutMergedActor)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("MergeStaticMeshActors failed. Internal error while creating the merged actor."));
|
|
return false;
|
|
}
|
|
|
|
OutMergedActor->GetStaticMeshComponent()->SetStaticMesh(MergedMesh);
|
|
OutMergedActor->SetActorLabel(MergeOptions.NewActorLabel);
|
|
AllActors[0]->GetWorld()->UpdateCullDistanceVolumes(OutMergedActor, OutMergedActor->GetStaticMeshComponent());
|
|
}
|
|
|
|
// Remove source actors
|
|
if (MergeOptions.bDestroySourceActors)
|
|
{
|
|
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
UWorld* World = AllActors[0]->GetWorld();
|
|
for (AActor* Actor : AllActors)
|
|
{
|
|
Layers->DisassociateActorFromLayers(Actor);
|
|
World->EditorDestroyActor(Actor, true);
|
|
}
|
|
}
|
|
|
|
//Select newly created actor
|
|
GEditor->SelectNone(false, true, false);
|
|
GEditor->SelectActor(OutMergedActor, true, false);
|
|
GEditor->NoteSelectionChange();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UStaticMeshEditorSubsystem::CreateProxyMeshActor(const TArray<class AStaticMeshActor*>& ActorsToMerge, const FCreateProxyMeshActorOptions& MergeOptions, class AStaticMeshActor*& OutMergedActor)
|
|
{
|
|
// See FMeshProxyTool::RunMerge (Engine\Source\Editor\MergeActors\Private\MeshProxyTool\MeshProxyTool.cpp)
|
|
TGuardValue<bool> UnattendedScriptGuard(GIsRunningUnattendedScript, true);
|
|
|
|
OutMergedActor = nullptr;
|
|
|
|
if (!EditorScriptingHelpers::CheckIfInEditorAndPIE())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UUnrealEditorSubsystem* UnrealEditorSubsystem = GEditor->GetEditorSubsystem<UUnrealEditorSubsystem>();
|
|
|
|
if (!UnrealEditorSubsystem)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FString FailureReason;
|
|
FString PackageName = EditorScriptingHelpers::ConvertAnyPathToLongPackagePath(MergeOptions.BasePackageName, FailureReason);
|
|
if (PackageName.IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("CreateProxyMeshActor. Failed to convert the BasePackageName. %s"), *FailureReason);
|
|
return false;
|
|
}
|
|
|
|
// Cleanup actors
|
|
TArray<AStaticMeshActor*> StaticMeshActors;
|
|
TArray<UPrimitiveComponent*> AllComponents_UNUSED;
|
|
FVector PivotLocation;
|
|
if (!InternalEditorMeshLibrary::FindValidActorAndComponents(ActorsToMerge, StaticMeshActors, AllComponents_UNUSED, PivotLocation, FailureReason))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("CreateProxyMeshActor failed. %s"), *FailureReason);
|
|
return false;
|
|
}
|
|
TArray<AActor*> AllActors(StaticMeshActors);
|
|
|
|
const IMeshMergeUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
|
|
|
|
FCreateProxyDelegate ProxyDelegate;
|
|
TArray<UObject*> CreatedAssets;
|
|
ProxyDelegate.BindLambda([&CreatedAssets](const FGuid Guid, TArray<UObject*>& InAssetsToSync) {CreatedAssets.Append(InAssetsToSync); });
|
|
|
|
MeshUtilities.CreateProxyMesh(
|
|
AllActors, // List of Actors to merge
|
|
MergeOptions.MeshProxySettings, // Merge settings
|
|
nullptr, // Base Material used for final proxy material. Note: nullptr for default impl: GEngine->DefaultFlattenMaterial
|
|
nullptr, // Package for generated assets. Note: if nullptr, BasePackageName is used
|
|
PackageName, // Will be used for naming generated assets, in case InOuter is not specified ProxyBasePackageName will be used as long package name for creating new packages
|
|
FGuid::NewGuid(), // Identify a job, First argument of the ProxyDelegate
|
|
ProxyDelegate // Called back on asset creation
|
|
);
|
|
|
|
UStaticMesh* MergedMesh = nullptr;
|
|
if (!CreatedAssets.FindItemByClass(&MergedMesh))
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("CreateProxyMeshActor failed. No mesh created."));
|
|
return false;
|
|
}
|
|
|
|
// Update the asset registry that a new static mesh and material has been created
|
|
FAssetRegistryModule& AssetRegistry = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
for (UObject* Asset : CreatedAssets)
|
|
{
|
|
AssetRegistry.AssetCreated(Asset);
|
|
GEditor->BroadcastObjectReimported(Asset);
|
|
}
|
|
|
|
// Also notify the content browser that the new assets exists
|
|
if (!IsRunningCommandlet())
|
|
{
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
ContentBrowserModule.Get().SyncBrowserToAssets(CreatedAssets, true);
|
|
}
|
|
|
|
// Place new mesh in the world
|
|
UWorld* ActorWorld = AllActors[0]->GetWorld();
|
|
ULevel* ActorLevel = AllActors[0]->GetLevel();
|
|
if (MergeOptions.bSpawnMergedActor)
|
|
{
|
|
FActorSpawnParameters Params;
|
|
Params.OverrideLevel = ActorLevel;
|
|
OutMergedActor = ActorWorld->SpawnActor<AStaticMeshActor>(FVector::ZeroVector, FRotator::ZeroRotator, Params);
|
|
if (!OutMergedActor)
|
|
{
|
|
UE_LOG(LogStaticMeshEditorSubsystem, Error, TEXT("CreateProxyMeshActor failed. Internal error while creating the merged actor."));
|
|
return false;
|
|
}
|
|
|
|
OutMergedActor->GetStaticMeshComponent()->SetStaticMesh(MergedMesh);
|
|
OutMergedActor->SetActorLabel(MergeOptions.NewActorLabel);
|
|
ActorWorld->UpdateCullDistanceVolumes(OutMergedActor, OutMergedActor->GetStaticMeshComponent());
|
|
}
|
|
|
|
// Remove source actors
|
|
if (MergeOptions.bDestroySourceActors)
|
|
{
|
|
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
for (AActor* Actor : AllActors)
|
|
{
|
|
Layers->DisassociateActorFromLayers(Actor);
|
|
ActorWorld->EditorDestroyActor(Actor, true);
|
|
}
|
|
}
|
|
|
|
//Select newly created actor
|
|
if (OutMergedActor)
|
|
{
|
|
GEditor->SelectNone(false, true, false);
|
|
GEditor->SelectActor(OutMergedActor, true, false); // don't notify but manually call NoteSelectionChange ?
|
|
GEditor->NoteSelectionChange();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|