3833 lines
145 KiB
C++
3833 lines
145 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshMergeUtilities.h"
|
|
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "MeshMerge/MeshInstancingSettings.h"
|
|
#include "MeshMerge/MeshMergingSettings.h"
|
|
#include "MeshMerge/MeshProxySettings.h"
|
|
#include "Engine/StaticMeshSocket.h"
|
|
|
|
#include "MaterialOptions.h"
|
|
#include "IMaterialBakingModule.h"
|
|
|
|
#include "Misc/PackageName.h"
|
|
#include "MaterialUtilities.h"
|
|
#include "Materials/MaterialInstanceConstant.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Components/SplineMeshComponent.h"
|
|
#include "Components/SkinnedMeshComponent.h"
|
|
#include "Components/ShapeComponent.h"
|
|
|
|
#include "SkeletalMeshTypes.h"
|
|
#include "SkeletalRenderPublic.h"
|
|
|
|
#include "UObject/UObjectBaseUtility.h"
|
|
#include "UObject/Package.h"
|
|
#include "Materials/Material.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "HierarchicalLODUtilitiesModule.h"
|
|
#include "MeshMergeData.h"
|
|
#include "IHierarchicalLODUtilities.h"
|
|
#include "Engine/MeshMergeCullingVolume.h"
|
|
|
|
#include "Landscape.h"
|
|
#include "LandscapeProxy.h"
|
|
|
|
#include "Editor.h"
|
|
#include "ProxyGenerationProcessor.h"
|
|
#include "Editor/EditorPerProjectUserSettings.h"
|
|
|
|
#include "Engine/StaticMesh.h"
|
|
#include "PhysicsEngine/ConvexElem.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "MeshUtilities.h"
|
|
#include "ImageUtils.h"
|
|
#include "LandscapeHeightfieldCollisionComponent.h"
|
|
#include "IMeshReductionManagerModule.h"
|
|
#include "IMeshReductionInterfaces.h"
|
|
|
|
#include "ProxyGenerationProcessor.h"
|
|
#include "IMaterialBakingAdapter.h"
|
|
#include "StaticMeshComponentLODInfo.h"
|
|
#include "SkeletalMeshAdapter.h"
|
|
#include "StaticMeshAdapter.h"
|
|
|
|
#include "MeshMergeDataTracker.h"
|
|
|
|
#include "Misc/FileHelper.h"
|
|
#include "MeshMergeHelpers.h"
|
|
#include "Settings/EditorExperimentalSettings.h"
|
|
#include "MaterialBakingStructures.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
#include "Engine/LODActor.h"
|
|
#include "HierarchicalLODVolume.h"
|
|
#include "Engine/Selection.h"
|
|
#include "MaterialBakingHelpers.h"
|
|
#include "IMeshMergeExtension.h"
|
|
|
|
#include "RawMesh.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshOperations.h"
|
|
#include "TriangleTypes.h"
|
|
#include "MaterialUtilities.h"
|
|
|
|
#include "Async/Future.h"
|
|
#include "Async/Async.h"
|
|
#include "TextureCompiler.h"
|
|
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
|
|
#include "ISMPartition/ISMComponentBatcher.h"
|
|
#include "ISMPartition/ISMComponentDescriptor.h"
|
|
|
|
#include "UObject/GCObjectScopeGuard.h"
|
|
|
|
#include "Algo/RemoveIf.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MeshMergeUtils"
|
|
|
|
DEFINE_LOG_CATEGORY(LogMeshMerging);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMeshMergeUtilitiesUVGenerationMethod(
|
|
TEXT("MeshMergeUtilities.UVGenerationMethod"),
|
|
0,
|
|
TEXT("UV generation method when creating merged or proxy meshes\n"
|
|
"0 - Engine default - (currently Patch Builder)\n"
|
|
"1 - Legacy\n"
|
|
"2 - UVAtlas\n"
|
|
"3 - XAtlas\n"
|
|
"4 - Patch Builder\n"));
|
|
|
|
static FStaticMeshOperations::EGenerateUVMethod GetUVGenerationMethodToUse()
|
|
{
|
|
switch (CVarMeshMergeUtilitiesUVGenerationMethod.GetValueOnAnyThread())
|
|
{
|
|
case 1: return FStaticMeshOperations::EGenerateUVMethod::Legacy;
|
|
case 2: return FStaticMeshOperations::EGenerateUVMethod::UVAtlas;
|
|
case 3: return FStaticMeshOperations::EGenerateUVMethod::XAtlas;
|
|
case 4: return FStaticMeshOperations::EGenerateUVMethod::PatchBuilder;
|
|
default: return FStaticMeshOperations::EGenerateUVMethod::Default;
|
|
}
|
|
}
|
|
|
|
FMeshMergeUtilities::FMeshMergeUtilities()
|
|
{
|
|
Processor = new FProxyGenerationProcessor(this);
|
|
}
|
|
|
|
FMeshMergeUtilities::~FMeshMergeUtilities()
|
|
{
|
|
FModuleManager::Get().OnModulesChanged().Remove(ModuleLoadedDelegateHandle);
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForComponent(TArray<TWeakObjectPtr<UObject>>& OptionObjects, IMaterialBakingAdapter* Adapter) const
|
|
{
|
|
// Try and find material (merge) options from provided set of objects
|
|
TWeakObjectPtr<UObject>* MaterialOptionsObject = OptionObjects.FindByPredicate([](TWeakObjectPtr<UObject> Object)
|
|
{
|
|
return Cast<UMaterialOptions>(Object.Get()) != nullptr;
|
|
});
|
|
|
|
TWeakObjectPtr<UObject>* MaterialMergeOptionsObject = OptionObjects.FindByPredicate([](TWeakObjectPtr<UObject> Object)
|
|
{
|
|
return Cast<UMaterialMergeOptions>(Object.Get()) != nullptr;
|
|
});
|
|
|
|
UMaterialOptions* MaterialOptions = MaterialOptionsObject ? Cast<UMaterialOptions>(MaterialOptionsObject->Get()) : nullptr;
|
|
checkf(MaterialOptions, TEXT("No valid material options found"));
|
|
|
|
|
|
UMaterialMergeOptions* MaterialMergeOptions = MaterialMergeOptionsObject ? Cast<UMaterialMergeOptions>(MaterialMergeOptionsObject->Get()) : nullptr;
|
|
|
|
// Mesh / LOD index
|
|
TMap<uint32, FMeshDescription> RawMeshLODs;
|
|
|
|
// Unique set of sections in mesh
|
|
TArray<FSectionInfo> UniqueSections;
|
|
|
|
TArray<FSectionInfo> Sections;
|
|
|
|
int32 NumLODs = Adapter->GetNumberOfLODs();
|
|
|
|
// LOD index, <original section index, unique section index>
|
|
TArray<TMap<int32, int32>> UniqueSectionIndexPerLOD;
|
|
UniqueSectionIndexPerLOD.AddDefaulted(NumLODs);
|
|
|
|
// Retrieve raw mesh data and unique sections
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
// Reset section for reuse
|
|
Sections.SetNum(0, EAllowShrinking::No);
|
|
|
|
// Extract raw mesh data
|
|
const bool bProcessedLOD = MaterialOptions->LODIndices.Contains(LODIndex);
|
|
if (bProcessedLOD)
|
|
{
|
|
FMeshDescription& RawMesh = RawMeshLODs.Add(LODIndex);
|
|
FStaticMeshAttributes(RawMesh).Register();
|
|
Adapter->RetrieveRawMeshData(LODIndex, RawMesh, MaterialOptions->bUseMeshData);
|
|
}
|
|
|
|
// Extract sections for given LOD index from the mesh
|
|
Adapter->RetrieveMeshSections(LODIndex, Sections);
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = Sections[SectionIndex];
|
|
Section.bProcessed = bProcessedLOD;
|
|
const int32 UniqueIndex = UniqueSections.AddUnique(Section);
|
|
UniqueSectionIndexPerLOD[LODIndex].Emplace(SectionIndex, UniqueIndex);
|
|
}
|
|
}
|
|
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
TMultiMap<uint32, uint32> UniqueMaterialToUniqueSectionMap;
|
|
// Populate list of unique materials and store section mappings
|
|
for (int32 SectionIndex = 0; SectionIndex < UniqueSections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = UniqueSections[SectionIndex];
|
|
const int32 UniqueIndex = UniqueMaterials.AddUnique(Section.Material);
|
|
UniqueMaterialToUniqueSectionMap.Add(UniqueIndex, SectionIndex);
|
|
}
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
TArray<TMap<uint32, uint32>> OutputMaterialsMap;
|
|
OutputMaterialsMap.AddDefaulted(NumLODs);
|
|
|
|
for (int32 MaterialIndex = 0; MaterialIndex < UniqueMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
UMaterialInterface* Material = UniqueMaterials[MaterialIndex];
|
|
|
|
// Retrieve all sections using this material
|
|
TArray<uint32> SectionIndices;
|
|
UniqueMaterialToUniqueSectionMap.MultiFind(MaterialIndex, SectionIndices);
|
|
|
|
if (MaterialOptions->bUseMeshData)
|
|
{
|
|
for (const int32 LODIndex : MaterialOptions->LODIndices)
|
|
{
|
|
FMeshData MeshSettings;
|
|
MeshSettings.MeshDescription = nullptr;
|
|
|
|
// Add material indices used for rendering out material
|
|
for (const auto& Pair : UniqueSectionIndexPerLOD[LODIndex])
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
MeshSettings.MaterialIndices.Add(Pair.Key);
|
|
}
|
|
}
|
|
|
|
if (MeshSettings.MaterialIndices.Num())
|
|
{
|
|
// Retrieve raw mesh
|
|
MeshSettings.MeshDescription = RawMeshLODs.Find(LODIndex);
|
|
|
|
//Should not be using mesh data if there is no mesh
|
|
check(MeshSettings.MeshDescription);
|
|
|
|
MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
const bool bUseVertexColor = FStaticMeshOperations::HasVertexColor(*(MeshSettings.MeshDescription));
|
|
if (MaterialOptions->bUseSpecificUVIndex)
|
|
{
|
|
MeshSettings.TextureCoordinateIndex = MaterialOptions->TextureCoordinateIndex;
|
|
}
|
|
// if you use vertex color, we can't rely on overlapping UV channel, so use light map UV to unwrap UVs
|
|
else if (bUseVertexColor)
|
|
{
|
|
MeshSettings.TextureCoordinateIndex = Adapter->LightmapUVIndex();
|
|
}
|
|
else
|
|
{
|
|
MeshSettings.TextureCoordinateIndex = 0;
|
|
}
|
|
|
|
Adapter->ApplySettings(LODIndex, MeshSettings);
|
|
|
|
// In case part of the UVs is not within the 0-1 range try to use the lightmap UVs
|
|
const bool bNeedsUniqueUVs = FMeshMergeHelpers::CheckWrappingUVs(*(MeshSettings.MeshDescription), MeshSettings.TextureCoordinateIndex);
|
|
const int32 LightMapUVIndex = Adapter->LightmapUVIndex();
|
|
|
|
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = FStaticMeshConstAttributes(*MeshSettings.MeshDescription).GetVertexInstanceUVs();
|
|
if (bNeedsUniqueUVs && MeshSettings.TextureCoordinateIndex != LightMapUVIndex && VertexInstanceUVs.GetNumElements() > 0 && VertexInstanceUVs.GetNumChannels() > LightMapUVIndex)
|
|
{
|
|
MeshSettings.TextureCoordinateIndex = LightMapUVIndex;
|
|
}
|
|
|
|
FMaterialData MaterialSettings;
|
|
MaterialSettings.Material = Material;
|
|
|
|
// Add all user defined properties for baking out
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (!Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
int32 NumTextureCoordinates;
|
|
bool bUsesVertexData;
|
|
Material->AnalyzeMaterialProperty(Entry.Property, NumTextureCoordinates, bUsesVertexData);
|
|
|
|
MaterialSettings.PropertySizes.Add(Entry.Property, Entry.bUseCustomSize ? Entry.CustomSize : MaterialOptions->TextureSize);
|
|
}
|
|
}
|
|
|
|
// For each original material index add an entry to the corresponding LOD and bake output index
|
|
for (int32 Index : MeshSettings.MaterialIndices)
|
|
{
|
|
OutputMaterialsMap[LODIndex].Emplace(Index, GlobalMeshSettings.Num());
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MeshSettings);
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are not using the mesh data we aren't doing anything special, just bake out uv range
|
|
FMeshData MeshSettings;
|
|
for (int32 LODIndex : MaterialOptions->LODIndices)
|
|
{
|
|
for (const auto& Pair : UniqueSectionIndexPerLOD[LODIndex])
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
MeshSettings.MaterialIndices.Add(Pair.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MeshSettings.MaterialIndices.Num())
|
|
{
|
|
MeshSettings.MeshDescription = nullptr;
|
|
MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
MeshSettings.TextureCoordinateIndex = 0;
|
|
|
|
FMaterialData MaterialSettings;
|
|
MaterialSettings.Material = Material;
|
|
|
|
// Add all user defined properties for baking out
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
// With substrate, we cannot use connection on the root node to infer Active or Constant properties. So we activate all material input for now.
|
|
// SUBSTRATE_TODO: gather information about the material used input, maybe using Material->GetSubstrateCompilationConfig().
|
|
const float bIsSubstrateEnabled = Substrate::IsSubstrateEnabled();
|
|
|
|
if ((bIsSubstrateEnabled || (!Entry.bUseConstantValue && Material->IsPropertyActive(Entry.Property))) && Entry.Property != MP_MAX)
|
|
{
|
|
MaterialSettings.PropertySizes.Add(Entry.Property, Entry.bUseCustomSize ? Entry.CustomSize : MaterialOptions->TextureSize);
|
|
}
|
|
}
|
|
|
|
for (int32 LODIndex : MaterialOptions->LODIndices)
|
|
{
|
|
for (const auto& Pair : UniqueSectionIndexPerLOD[LODIndex])
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
/// For each original material index add an entry to the corresponding LOD and bake output index
|
|
OutputMaterialsMap[LODIndex].Emplace(Pair.Key, GlobalMeshSettings.Num());
|
|
}
|
|
}
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MeshSettings);
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FMeshData*> MeshSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMeshSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MeshSettingPtrs.Add(&GlobalMeshSettings[SettingsIndex]);
|
|
}
|
|
|
|
TArray<FMaterialData*> MaterialSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMaterialSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MaterialSettingPtrs.Add(&GlobalMaterialSettings[SettingsIndex]);
|
|
}
|
|
|
|
TArray<FBakeOutput> BakeOutputs;
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
Module.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs);
|
|
|
|
// Append constant properties which did not require baking out
|
|
TArray<FColor> ConstantData;
|
|
FIntPoint ConstantSize(1, 1);
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
ConstantData.SetNum(1, EAllowShrinking::No);
|
|
ConstantData[0] = FColor(Entry.ConstantValue * 255.0f, Entry.ConstantValue * 255.0f, Entry.ConstantValue * 255.0f);
|
|
for (FBakeOutput& Ouput : BakeOutputs)
|
|
{
|
|
Ouput.PropertyData.Add(Entry.Property, ConstantData);
|
|
Ouput.PropertySizes.Add(Entry.Property, ConstantSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<UMaterialInterface*> NewMaterials;
|
|
|
|
FString PackageName = Adapter->GetBaseName();
|
|
|
|
const FGuid NameGuid = FGuid::NewGuid();
|
|
for (int32 OutputIndex = 0; OutputIndex < BakeOutputs.Num(); ++OutputIndex)
|
|
{
|
|
// Create merged material asset
|
|
FString MaterialAssetName = TEXT("M_") + FPackageName::GetShortName(PackageName) + TEXT("_") + MaterialSettingPtrs[OutputIndex]->Material->GetName() + TEXT("_") + NameGuid.ToString();
|
|
FString MaterialPackageName = FPackageName::GetLongPackagePath(PackageName) + TEXT("/") + MaterialAssetName;
|
|
|
|
FBakeOutput& Output = BakeOutputs[OutputIndex];
|
|
// Optimize output
|
|
for (auto DataPair : Output.PropertyData)
|
|
{
|
|
FMaterialUtilities::OptimizeSampleArray(DataPair.Value, Output.PropertySizes[DataPair.Key]);
|
|
}
|
|
|
|
UMaterialInterface* Material = nullptr;
|
|
|
|
if (Adapter->GetOuter())
|
|
{
|
|
Material = FMaterialUtilities::CreateProxyMaterialAndTextures(Adapter->GetOuter(), MaterialAssetName, Output, *MeshSettingPtrs[OutputIndex], *MaterialSettingPtrs[OutputIndex], MaterialOptions);
|
|
}
|
|
else
|
|
{
|
|
Material = FMaterialUtilities::CreateProxyMaterialAndTextures(MaterialPackageName, MaterialAssetName, Output, *MeshSettingPtrs[OutputIndex], *MaterialSettingPtrs[OutputIndex], MaterialOptions);
|
|
}
|
|
|
|
|
|
NewMaterials.Add(Material);
|
|
}
|
|
|
|
// Retrieve material indices which were not baked out and should still be part of the final asset
|
|
TArray<int32> NonReplaceMaterialIndices;
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
const bool bProcessedLOD = MaterialOptions->LODIndices.Contains(LODIndex);
|
|
if (!bProcessedLOD)
|
|
{
|
|
for (const auto& Pair : UniqueSectionIndexPerLOD[LODIndex])
|
|
{
|
|
NonReplaceMaterialIndices.AddUnique(Adapter->GetMaterialIndex(LODIndex, Pair.Key));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remap all baked out materials to their new material indices
|
|
TMap<uint32, uint32> NewMaterialRemap;
|
|
for (int32 LODIndex : MaterialOptions->LODIndices)
|
|
{
|
|
// Key == original section index, Value == unique material index
|
|
for (const auto& Pair : OutputMaterialsMap[LODIndex])
|
|
{
|
|
int32 SetIndex = Adapter->GetMaterialIndex(LODIndex, Pair.Key);
|
|
if (!NonReplaceMaterialIndices.Contains(SetIndex))
|
|
{
|
|
Adapter->SetMaterial(SetIndex, NewMaterials[Pair.Value]);
|
|
}
|
|
else
|
|
{
|
|
// Check if this material was processed and a new entry already exists
|
|
if (uint32* ExistingIndex = NewMaterialRemap.Find(Pair.Value))
|
|
{
|
|
Adapter->RemapMaterialIndex(LODIndex, Pair.Key, *ExistingIndex);
|
|
}
|
|
else
|
|
{
|
|
// Add new material
|
|
int32 NewMaterialIndex = INDEX_NONE;
|
|
if (Adapter->GetMaterialSlotName(Pair.Key).IsNone() || Adapter->GetImportedMaterialSlotName(Pair.Key).IsNone())
|
|
{
|
|
NewMaterialIndex = Adapter->AddMaterial(NewMaterials[Pair.Value]);
|
|
}
|
|
else
|
|
{
|
|
NewMaterialIndex = Adapter->AddMaterial(NewMaterials[Pair.Value], Adapter->GetMaterialSlotName(Pair.Key), Adapter->GetImportedMaterialSlotName(Pair.Key));
|
|
}
|
|
|
|
NewMaterialRemap.Add(Pair.Value, NewMaterialIndex);
|
|
Adapter->RemapMaterialIndex(LODIndex, Pair.Key, NewMaterialIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Adapter->UpdateUVChannelData();
|
|
GlobalMeshSettings.Empty();
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForComponent(USkeletalMeshComponent* SkeletalMeshComponent) const
|
|
{
|
|
if (!SkeletalMeshComponent || !SkeletalMeshComponent->GetSkeletalMeshAsset())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve settings object
|
|
UMaterialOptions* MaterialOptions = DuplicateObject(GetMutableDefault<UMaterialOptions>(), GetTransientPackage());
|
|
UAssetBakeOptions* AssetOptions = GetMutableDefault<UAssetBakeOptions>();
|
|
UMaterialMergeOptions* MergeOptions = GetMutableDefault<UMaterialMergeOptions>();
|
|
TArray<TWeakObjectPtr<UObject>> Objects{ MergeOptions, AssetOptions, MaterialOptions };
|
|
|
|
const int32 NumLODs = SkeletalMeshComponent->GetSkeletalMeshAsset()->GetLODNum();
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
if (!Module.SetupMaterialBakeSettings(Objects, NumLODs))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bake out materials for skeletal mesh
|
|
SkeletalMeshComponent->GetSkeletalMeshAsset()->Modify();
|
|
FSkeletalMeshComponentAdapter Adapter(SkeletalMeshComponent);
|
|
BakeMaterialsForComponent(Objects, &Adapter);
|
|
SkeletalMeshComponent->MarkRenderStateDirty();
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForComponent(UStaticMeshComponent* StaticMeshComponent) const
|
|
{
|
|
if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve settings object
|
|
UMaterialOptions* MaterialOptions = DuplicateObject(GetMutableDefault<UMaterialOptions>(), GetTransientPackage());
|
|
UAssetBakeOptions* AssetOptions = GetMutableDefault<UAssetBakeOptions>();
|
|
UMaterialMergeOptions* MergeOptions = GetMutableDefault<UMaterialMergeOptions>();
|
|
TArray<TWeakObjectPtr<UObject>> Objects{ MergeOptions, AssetOptions, MaterialOptions };
|
|
|
|
const int32 NumLODs = StaticMeshComponent->GetStaticMesh()->GetNumLODs();
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
if (!Module.SetupMaterialBakeSettings(Objects, NumLODs))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bake out materials for static mesh component
|
|
FStaticMeshComponentAdapter Adapter(StaticMeshComponent);
|
|
BakeMaterialsForComponent(Objects, &Adapter);
|
|
StaticMeshComponent->MarkRenderStateDirty();
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForMesh(UStaticMesh* StaticMesh) const
|
|
{
|
|
if (!StaticMesh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Retrieve settings object
|
|
UMaterialOptions* MaterialOptions = DuplicateObject(GetMutableDefault<UMaterialOptions>(), GetTransientPackage());
|
|
UAssetBakeOptions* AssetOptions = GetMutableDefault<UAssetBakeOptions>();
|
|
UMaterialMergeOptions* MergeOptions = GetMutableDefault<UMaterialMergeOptions>();
|
|
TArray<TWeakObjectPtr<UObject>> Objects{ MergeOptions, AssetOptions, MaterialOptions };
|
|
|
|
const int32 NumLODs = StaticMesh->GetNumLODs();
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
if (!Module.SetupMaterialBakeSettings(Objects, NumLODs))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bake out materials for static mesh asset
|
|
StaticMesh->Modify();
|
|
FStaticMeshAdapter Adapter(StaticMesh);
|
|
BakeMaterialsForComponent(Objects, &Adapter);
|
|
}
|
|
|
|
|
|
static bool DetermineMaterialVertexDataUsage(UMaterialInterface* Material, const UMaterialOptions* MaterialOptions)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(DetermineMaterialVertexDataUsage);
|
|
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
// Don't have to check a property if the result is going to be constant anyway
|
|
if (!Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
int32 NumTextureCoordinates;
|
|
bool bUsesVertexData;
|
|
Material->AnalyzeMaterialProperty(Entry.Property, NumTextureCoordinates, bUsesVertexData);
|
|
|
|
if (bUsesVertexData || NumTextureCoordinates > 1)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FMeshMergeUtilities::ConvertOutputToFlatMaterials(const TArray<FBakeOutput>& BakeOutputs, const TArray<FMaterialData>& MaterialData, TArray<FFlattenMaterial> &FlattenedMaterials) const
|
|
{
|
|
for (int32 OutputIndex = 0; OutputIndex < BakeOutputs.Num(); ++OutputIndex)
|
|
{
|
|
const FBakeOutput& Output = BakeOutputs[OutputIndex];
|
|
const FMaterialData& MaterialInfo = MaterialData[OutputIndex];
|
|
|
|
FFlattenMaterial Material;
|
|
|
|
for (TPair<EMaterialProperty, FIntPoint> SizePair : Output.PropertySizes)
|
|
{
|
|
EFlattenMaterialProperties OldProperty = ToFlattenProperty(SizePair.Key);
|
|
if (ensure(OldProperty != EFlattenMaterialProperties::NumFlattenMaterialProperties))
|
|
{
|
|
Material.SetPropertySize(OldProperty, SizePair.Value);
|
|
Material.GetPropertySamples(OldProperty).Append(Output.PropertyData[SizePair.Key]);
|
|
}
|
|
}
|
|
|
|
Material.bDitheredLODTransition = MaterialInfo.Material->IsDitheredLODTransition();
|
|
Material.BlendMode = BLEND_Opaque;
|
|
Material.bTwoSided = MaterialInfo.Material->IsTwoSided();
|
|
Material.EmissiveScale = Output.EmissiveScale;
|
|
|
|
FlattenedMaterials.Add(Material);
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::TransferOutputToFlatMaterials(const TArray<FMaterialData>& InMaterialData, TArray<FBakeOutput>& InOutBakeOutputs, TArray<FFlattenMaterial> &OutFlattenedMaterials) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::TransferOutputToFlatMaterials)
|
|
|
|
OutFlattenedMaterials.SetNum(InOutBakeOutputs.Num());
|
|
|
|
for (int32 OutputIndex = 0; OutputIndex < InOutBakeOutputs.Num(); ++OutputIndex)
|
|
{
|
|
FBakeOutput& Output = InOutBakeOutputs[OutputIndex];
|
|
const FMaterialData& MaterialInfo = InMaterialData[OutputIndex];
|
|
|
|
FFlattenMaterial& Material = OutFlattenedMaterials[OutputIndex];
|
|
|
|
for (TPair<EMaterialProperty, FIntPoint> SizePair : Output.PropertySizes)
|
|
{
|
|
EFlattenMaterialProperties OldProperty = ToFlattenProperty(SizePair.Key);
|
|
if (ensure(OldProperty != EFlattenMaterialProperties::NumFlattenMaterialProperties))
|
|
{
|
|
Material.SetPropertySize(OldProperty, SizePair.Value);
|
|
Material.GetPropertySamples(OldProperty) = MoveTemp(Output.PropertyData[SizePair.Key]);
|
|
}
|
|
}
|
|
|
|
Material.bDitheredLODTransition = MaterialInfo.Material->IsDitheredLODTransition();
|
|
Material.BlendMode = BLEND_Opaque;
|
|
Material.bTwoSided = MaterialInfo.Material->IsTwoSided();
|
|
Material.EmissiveScale = Output.EmissiveScale;
|
|
}
|
|
}
|
|
|
|
EFlattenMaterialProperties FMeshMergeUtilities::ToFlattenProperty(EMaterialProperty MaterialProperty) const
|
|
{
|
|
switch (MaterialProperty)
|
|
{
|
|
case EMaterialProperty::MP_BaseColor: return EFlattenMaterialProperties::Diffuse;
|
|
case EMaterialProperty::MP_Metallic: return EFlattenMaterialProperties::Metallic;
|
|
case EMaterialProperty::MP_Specular: return EFlattenMaterialProperties::Specular;
|
|
case EMaterialProperty::MP_Roughness: return EFlattenMaterialProperties::Roughness;
|
|
case EMaterialProperty::MP_Anisotropy: return EFlattenMaterialProperties::Anisotropy;
|
|
case EMaterialProperty::MP_Normal: return EFlattenMaterialProperties::Normal;
|
|
case EMaterialProperty::MP_Tangent: return EFlattenMaterialProperties::Tangent;
|
|
case EMaterialProperty::MP_Opacity: return EFlattenMaterialProperties::Opacity;
|
|
case EMaterialProperty::MP_EmissiveColor: return EFlattenMaterialProperties::Emissive;
|
|
case EMaterialProperty::MP_OpacityMask: return EFlattenMaterialProperties::OpacityMask;
|
|
case EMaterialProperty::MP_AmbientOcclusion: return EFlattenMaterialProperties::AmbientOcclusion;
|
|
default: return EFlattenMaterialProperties::NumFlattenMaterialProperties;
|
|
}
|
|
}
|
|
|
|
UMaterialOptions* FMeshMergeUtilities::PopulateMaterialOptions(const FMaterialProxySettings& MaterialSettings) const
|
|
{
|
|
UMaterialOptions* MaterialOptions = DuplicateObject(GetMutableDefault<UMaterialOptions>(), GetTransientPackage());
|
|
MaterialOptions->Properties.Empty();
|
|
MaterialOptions->TextureSize = MaterialSettings.TextureSize;
|
|
|
|
FPropertyEntry Property;
|
|
PopulatePropertyEntry(MaterialSettings, MP_BaseColor, Property);
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Specular, Property);
|
|
if (MaterialSettings.bSpecularMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Roughness, Property);
|
|
if (MaterialSettings.bRoughnessMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Anisotropy, Property);
|
|
if (MaterialSettings.bAnisotropyMap)
|
|
{
|
|
MaterialOptions->Properties.Add(Property);
|
|
}
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Metallic, Property);
|
|
if (MaterialSettings.bMetallicMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Normal, Property);
|
|
if (MaterialSettings.bNormalMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Tangent, Property);
|
|
if (MaterialSettings.bTangentMap)
|
|
{
|
|
MaterialOptions->Properties.Add(Property);
|
|
}
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Opacity, Property);
|
|
if (MaterialSettings.bOpacityMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_OpacityMask, Property);
|
|
if (MaterialSettings.bOpacityMaskMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_EmissiveColor, Property);
|
|
if (MaterialSettings.bEmissiveMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_AmbientOcclusion, Property);
|
|
if (MaterialSettings.bAmbientOcclusionMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
return MaterialOptions;
|
|
}
|
|
|
|
void FMeshMergeUtilities::PopulatePropertyEntry(const FMaterialProxySettings& MaterialSettings, EMaterialProperty MaterialProperty, FPropertyEntry& InOutPropertyEntry) const
|
|
{
|
|
InOutPropertyEntry.Property = MaterialProperty;
|
|
switch (MaterialSettings.TextureSizingType)
|
|
{
|
|
/** Set property output size to unique per-property user set sizes */
|
|
case TextureSizingType_UseManualOverrideTextureSize:
|
|
{
|
|
InOutPropertyEntry.bUseCustomSize = true;
|
|
InOutPropertyEntry.CustomSize = [MaterialSettings, MaterialProperty]() -> FIntPoint
|
|
{
|
|
switch (MaterialProperty)
|
|
{
|
|
case MP_BaseColor: return MaterialSettings.DiffuseTextureSize;
|
|
case MP_Specular: return MaterialSettings.SpecularTextureSize;
|
|
case MP_Roughness: return MaterialSettings.RoughnessTextureSize;
|
|
case MP_Anisotropy: return MaterialSettings.AnisotropyTextureSize;
|
|
case MP_Metallic: return MaterialSettings.MetallicTextureSize;
|
|
case MP_Normal: return MaterialSettings.NormalTextureSize;
|
|
case MP_Tangent: return MaterialSettings.TangentTextureSize;
|
|
case MP_Opacity: return MaterialSettings.OpacityTextureSize;
|
|
case MP_OpacityMask: return MaterialSettings.OpacityMaskTextureSize;
|
|
case MP_EmissiveColor: return MaterialSettings.EmissiveTextureSize;
|
|
case MP_AmbientOcclusion: return MaterialSettings.AmbientOcclusionTextureSize;
|
|
default:
|
|
{
|
|
checkf(false, TEXT("Invalid Material Property"));
|
|
return FIntPoint();
|
|
}
|
|
}
|
|
}();
|
|
|
|
break;
|
|
}
|
|
/** Set property output size to biased values off the TextureSize value (Normal at fullres, Diffuse at halfres, and anything else at quarter res */
|
|
case TextureSizingType_UseAutomaticBiasedSizes:
|
|
{
|
|
const FIntPoint FullRes = MaterialSettings.TextureSize;
|
|
const FIntPoint HalfRes = FIntPoint(FMath::Max(8, FullRes.X >> 1), FMath::Max(8, FullRes.Y >> 1));
|
|
const FIntPoint QuarterRes = FIntPoint(FMath::Max(4, FullRes.X >> 2), FMath::Max(4, FullRes.Y >> 2));
|
|
|
|
InOutPropertyEntry.bUseCustomSize = true;
|
|
InOutPropertyEntry.CustomSize = [FullRes, HalfRes, QuarterRes, MaterialSettings, MaterialProperty]() -> FIntPoint
|
|
{
|
|
switch (MaterialProperty)
|
|
{
|
|
case MP_Normal: return FullRes;
|
|
case MP_Tangent: return HalfRes;
|
|
case MP_BaseColor: return HalfRes;
|
|
case MP_Specular: return QuarterRes;
|
|
case MP_Roughness: return QuarterRes;
|
|
case MP_Anisotropy: return QuarterRes;
|
|
case MP_Metallic: return QuarterRes;
|
|
case MP_Opacity: return QuarterRes;
|
|
case MP_OpacityMask: return QuarterRes;
|
|
case MP_EmissiveColor: return QuarterRes;
|
|
case MP_AmbientOcclusion: return QuarterRes;
|
|
default:
|
|
{
|
|
checkf(false, TEXT("Invalid Material Property"));
|
|
return FIntPoint();
|
|
}
|
|
}
|
|
}();
|
|
|
|
break;
|
|
}
|
|
/** Set all sizes to TextureSize */
|
|
case TextureSizingType_UseSingleTextureSize:
|
|
case TextureSizingType_UseSimplygonAutomaticSizing:
|
|
{
|
|
InOutPropertyEntry.bUseCustomSize = false;
|
|
InOutPropertyEntry.CustomSize = MaterialSettings.TextureSize;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UE_LOG(LogMeshMerging, Error, TEXT("Unsupported TextureSizingType value. You should resolve the material texture size first with ResolveTextureSize()"));
|
|
|
|
}
|
|
/** Check whether or not a constant value should be used for this property */
|
|
InOutPropertyEntry.bUseConstantValue = [MaterialSettings, MaterialProperty]() -> bool
|
|
{
|
|
switch (MaterialProperty)
|
|
{
|
|
case MP_BaseColor: return false;
|
|
case MP_Normal: return !MaterialSettings.bNormalMap;
|
|
case MP_Tangent: return !MaterialSettings.bTangentMap;
|
|
case MP_Specular: return !MaterialSettings.bSpecularMap;
|
|
case MP_Roughness: return !MaterialSettings.bRoughnessMap;
|
|
case MP_Anisotropy: return !MaterialSettings.bAnisotropyMap;
|
|
case MP_Metallic: return !MaterialSettings.bMetallicMap;
|
|
case MP_Opacity: return !MaterialSettings.bOpacityMap;
|
|
case MP_OpacityMask: return !MaterialSettings.bOpacityMaskMap;
|
|
case MP_EmissiveColor: return !MaterialSettings.bEmissiveMap;
|
|
case MP_AmbientOcclusion: return !MaterialSettings.bAmbientOcclusionMap;
|
|
default:
|
|
{
|
|
checkf(false, TEXT("Invalid Material Property"));
|
|
return false;
|
|
}
|
|
}
|
|
}();
|
|
/** Set the value if a constant value should be used for this property */
|
|
InOutPropertyEntry.ConstantValue = [MaterialSettings, MaterialProperty]() -> float
|
|
{
|
|
switch (MaterialProperty)
|
|
{
|
|
case MP_BaseColor: return 1.0f;
|
|
case MP_Normal: return 1.0f;
|
|
case MP_Tangent: return 1.0f;
|
|
case MP_Specular: return MaterialSettings.SpecularConstant;
|
|
case MP_Roughness: return MaterialSettings.RoughnessConstant;
|
|
case MP_Anisotropy: return MaterialSettings.AnisotropyConstant;
|
|
case MP_Metallic: return MaterialSettings.MetallicConstant;
|
|
case MP_Opacity: return MaterialSettings.OpacityConstant;
|
|
case MP_OpacityMask: return MaterialSettings.OpacityMaskConstant;
|
|
case MP_EmissiveColor: return 0.0f;
|
|
case MP_AmbientOcclusion: return MaterialSettings.AmbientOcclusionConstant;
|
|
default:
|
|
{
|
|
checkf(false, TEXT("Invalid Material Property"));
|
|
return 1.0f;
|
|
}
|
|
}
|
|
}();
|
|
}
|
|
|
|
void FMeshMergeUtilities::CopyTextureRect(const FColor* Src, const FIntPoint& SrcSize, FColor* Dst, const FIntPoint& DstSize, const FIntPoint& DstPos, bool bCopyOnlyMaskedPixels) const
|
|
{
|
|
const int32 RowLength = SrcSize.X * sizeof(FColor);
|
|
FColor* RowDst = Dst + DstSize.X*DstPos.Y;
|
|
const FColor* RowSrc = Src;
|
|
if(bCopyOnlyMaskedPixels)
|
|
{
|
|
for (int32 RowIdx = 0; RowIdx < SrcSize.Y; ++RowIdx)
|
|
{
|
|
for (int32 ColIdx = 0; ColIdx < SrcSize.X; ++ColIdx)
|
|
{
|
|
if(RowSrc[ColIdx] != FColor::Magenta)
|
|
{
|
|
RowDst[DstPos.X + ColIdx] = RowSrc[ColIdx];
|
|
}
|
|
}
|
|
|
|
RowDst += DstSize.X;
|
|
RowSrc += SrcSize.X;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 RowIdx = 0; RowIdx < SrcSize.Y; ++RowIdx)
|
|
{
|
|
FMemory::Memcpy(RowDst + DstPos.X, RowSrc, RowLength);
|
|
RowDst += DstSize.X;
|
|
RowSrc += SrcSize.X;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::SetTextureRect(const FColor& ColorValue, const FIntPoint& SrcSize, FColor* Dst, const FIntPoint& DstSize, const FIntPoint& DstPos) const
|
|
{
|
|
FColor* RowDst = Dst + DstSize.X*DstPos.Y;
|
|
|
|
for (int32 RowIdx = 0; RowIdx < SrcSize.Y; ++RowIdx)
|
|
{
|
|
for (int32 ColIdx = 0; ColIdx < SrcSize.X; ++ColIdx)
|
|
{
|
|
RowDst[DstPos.X + ColIdx] = ColorValue;
|
|
}
|
|
|
|
RowDst += DstSize.X;
|
|
}
|
|
}
|
|
|
|
FIntPoint FMeshMergeUtilities::ConditionalImageResize(const FIntPoint& SrcSize, const FIntPoint& DesiredSize, TArray<FColor>& InOutImage, bool bLinearSpace) const
|
|
{
|
|
const int32 NumDesiredSamples = DesiredSize.X*DesiredSize.Y;
|
|
if (InOutImage.Num() && InOutImage.Num() != NumDesiredSamples)
|
|
{
|
|
check(InOutImage.Num() == SrcSize.X*SrcSize.Y);
|
|
TArray<FColor> OutImage;
|
|
if (NumDesiredSamples > 0)
|
|
{
|
|
const bool bOpaqueAlpha = true; // ??
|
|
FImageUtils::ImageResize(SrcSize.X, SrcSize.Y, InOutImage, DesiredSize.X, DesiredSize.Y, OutImage, bLinearSpace, bOpaqueAlpha);
|
|
}
|
|
Exchange(InOutImage, OutImage);
|
|
return DesiredSize;
|
|
}
|
|
|
|
return SrcSize;
|
|
}
|
|
|
|
void FMeshMergeUtilities::MergeFlattenedMaterials(TArray<struct FFlattenMaterial>& InMaterialList, int32 InGutter, FFlattenMaterial& OutMergedMaterial, TArray<FUVOffsetScalePair>& OutUVTransforms) const
|
|
{
|
|
// Fill output UV transforms with invalid values
|
|
OutUVTransforms.SetNumZeroed(InMaterialList.Num());
|
|
|
|
const int32 AtlasGridSize = FMath::CeilToInt(FMath::Sqrt(static_cast<float>(InMaterialList.Num())));
|
|
OutMergedMaterial.EmissiveScale = FlattenEmissivescale(InMaterialList);
|
|
|
|
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
|
|
{
|
|
const EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
|
|
if (OutMergedMaterial.ShouldGenerateDataForProperty(Property))
|
|
{
|
|
const FIntPoint AtlasTextureSize = OutMergedMaterial.GetPropertySize(Property);
|
|
const FIntPoint ExportTextureSize = AtlasTextureSize / AtlasGridSize;
|
|
const int32 AtlasNumSamples = AtlasTextureSize.X*AtlasTextureSize.Y;
|
|
check(OutMergedMaterial.GetPropertySize(Property) == AtlasTextureSize);
|
|
TArray<FColor>& Samples = OutMergedMaterial.GetPropertySamples(Property);
|
|
Samples.SetNumUninitialized(AtlasNumSamples);
|
|
|
|
// Fill with magenta (as we will be box blurring this later)
|
|
for(FColor& SampleColor : Samples)
|
|
{
|
|
SampleColor = FColor(255, 0, 255);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 AtlasRowIdx = 0;
|
|
int32 AtlasColIdx = 0;
|
|
FIntPoint Gutter(InGutter, InGutter);
|
|
FIntPoint DoubleGutter(InGutter * 2, InGutter * 2);
|
|
FIntPoint GlobalAtlasTargetPos = Gutter;
|
|
|
|
bool bSamplesWritten[(uint32)EFlattenMaterialProperties::NumFlattenMaterialProperties];
|
|
FMemory::Memset(bSamplesWritten, 0);
|
|
|
|
// Used to calculate UV transforms
|
|
const FIntPoint GlobalAtlasTextureSize = OutMergedMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse);
|
|
const FIntPoint GlobalExportTextureSize = (GlobalAtlasTextureSize / AtlasGridSize) - DoubleGutter;
|
|
const FIntPoint GlobalExportEntrySize = (GlobalAtlasTextureSize / AtlasGridSize);
|
|
|
|
// Flatten all materials and merge them into one material using texture atlases
|
|
for (int32 MatIdx = 0; MatIdx < InMaterialList.Num(); ++MatIdx)
|
|
{
|
|
FFlattenMaterial& FlatMaterial = InMaterialList[MatIdx];
|
|
OutMergedMaterial.bTwoSided |= FlatMaterial.bTwoSided;
|
|
OutMergedMaterial.bDitheredLODTransition = FlatMaterial.bDitheredLODTransition;
|
|
|
|
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
|
|
{
|
|
const EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
|
|
const FIntPoint PropertyTextureSize = OutMergedMaterial.GetPropertySize(Property);
|
|
const int32 NumPropertySamples = PropertyTextureSize.X*PropertyTextureSize.Y;
|
|
|
|
const FIntPoint PropertyAtlasTextureSize = (PropertyTextureSize / AtlasGridSize) - DoubleGutter;
|
|
const FIntPoint PropertyAtlasEntrySize = (PropertyTextureSize / AtlasGridSize);
|
|
const FIntPoint AtlasTargetPos((AtlasColIdx * PropertyAtlasEntrySize.X) + InGutter, (AtlasRowIdx * PropertyAtlasEntrySize.Y) + InGutter);
|
|
|
|
if (OutMergedMaterial.ShouldGenerateDataForProperty(Property) && FlatMaterial.DoesPropertyContainData(Property))
|
|
{
|
|
TArray<FColor>& SourceSamples = FlatMaterial.GetPropertySamples(Property);
|
|
TArray<FColor>& TargetSamples = OutMergedMaterial.GetPropertySamples(Property);
|
|
if (FlatMaterial.IsPropertyConstant(Property))
|
|
{
|
|
SetTextureRect(SourceSamples[0], PropertyAtlasTextureSize, TargetSamples.GetData(), PropertyTextureSize, AtlasTargetPos);
|
|
}
|
|
else
|
|
{
|
|
FIntPoint PropertySize = FlatMaterial.GetPropertySize(Property);
|
|
PropertySize = ConditionalImageResize(PropertySize, PropertyAtlasTextureSize, SourceSamples, false);
|
|
CopyTextureRect(SourceSamples.GetData(), PropertyAtlasTextureSize, TargetSamples.GetData(), PropertyTextureSize, AtlasTargetPos);
|
|
FlatMaterial.SetPropertySize(Property, PropertySize);
|
|
}
|
|
|
|
bSamplesWritten[PropertyIndex] |= true;
|
|
}
|
|
}
|
|
|
|
check(OutUVTransforms.IsValidIndex(MatIdx));
|
|
|
|
// Offset
|
|
OutUVTransforms[MatIdx].Key = FVector2D(
|
|
(float)GlobalAtlasTargetPos.X / GlobalAtlasTextureSize.X,
|
|
(float)GlobalAtlasTargetPos.Y / GlobalAtlasTextureSize.Y);
|
|
|
|
// Scale
|
|
OutUVTransforms[MatIdx].Value = FVector2D(
|
|
(float)GlobalExportTextureSize.X / GlobalAtlasTextureSize.X,
|
|
(float)GlobalExportTextureSize.Y / GlobalAtlasTextureSize.Y);
|
|
|
|
AtlasColIdx++;
|
|
if (AtlasColIdx >= AtlasGridSize)
|
|
{
|
|
AtlasColIdx = 0;
|
|
AtlasRowIdx++;
|
|
}
|
|
|
|
GlobalAtlasTargetPos = FIntPoint((AtlasColIdx * GlobalExportEntrySize.X) + InGutter, (AtlasRowIdx * GlobalExportEntrySize.Y) + InGutter);
|
|
}
|
|
|
|
// Check if some properties weren't populated with data (which means we can empty them out)
|
|
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
|
|
{
|
|
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
|
|
if (!bSamplesWritten[PropertyIndex])
|
|
{
|
|
OutMergedMaterial.GetPropertySamples(Property).Empty();
|
|
OutMergedMaterial.SetPropertySize(Property, FIntPoint(0, 0));
|
|
}
|
|
else
|
|
{
|
|
// Smear borders
|
|
const FIntPoint PropertySize = OutMergedMaterial.GetPropertySize(Property);
|
|
FMaterialBakingHelpers::PerformUVBorderSmear(OutMergedMaterial.GetPropertySamples(Property), PropertySize.X, PropertySize.Y);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void FMeshMergeUtilities::FlattenBinnedMaterials(TArray<struct FFlattenMaterial>& InMaterialList, const TArray<FBox2D>& InMaterialBoxes, int32 InGutter, bool bCopyOnlyMaskedPixels, FFlattenMaterial& OutMergedMaterial, TArray<FUVOffsetScalePair>& OutUVTransforms) const
|
|
{
|
|
// Fill output UV transforms with invalid values
|
|
OutUVTransforms.SetNumZeroed(InMaterialList.Num());
|
|
|
|
// Flatten emissive scale across all incoming materials
|
|
OutMergedMaterial.EmissiveScale = FlattenEmissivescale(InMaterialList);
|
|
|
|
// Merge all material properties
|
|
for (int32 Index = 0; Index < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++Index)
|
|
{
|
|
const EFlattenMaterialProperties Property = (EFlattenMaterialProperties)Index;
|
|
const FIntPoint& OutTextureSize = OutMergedMaterial.GetPropertySize(Property);
|
|
if (OutTextureSize != FIntPoint::ZeroValue)
|
|
{
|
|
TArray<FColor>& OutSamples = OutMergedMaterial.GetPropertySamples(Property);
|
|
OutSamples.Reserve(OutTextureSize.X * OutTextureSize.Y);
|
|
OutSamples.SetNumUninitialized(OutTextureSize.X * OutTextureSize.Y);
|
|
|
|
// Fill with magenta (as we will be box blurring this later)
|
|
for(FColor& SampleColor : OutSamples)
|
|
{
|
|
SampleColor = FColor(255, 0, 255);
|
|
}
|
|
|
|
FVector2D Gutter2D((float)InGutter, (float)InGutter);
|
|
bool bMaterialsWritten = false;
|
|
for (int32 MaterialIndex = 0; MaterialIndex < InMaterialList.Num(); ++MaterialIndex)
|
|
{
|
|
// Determine output size and offset
|
|
FFlattenMaterial& FlatMaterial = InMaterialList[MaterialIndex];
|
|
OutMergedMaterial.bDitheredLODTransition |= FlatMaterial.bDitheredLODTransition;
|
|
OutMergedMaterial.bTwoSided |= FlatMaterial.bTwoSided;
|
|
|
|
if (FlatMaterial.DoesPropertyContainData(Property))
|
|
{
|
|
const FBox2D MaterialBox = InMaterialBoxes[MaterialIndex];
|
|
const FIntPoint& InputSize = FlatMaterial.GetPropertySize(Property);
|
|
TArray<FColor>& InputSamples = FlatMaterial.GetPropertySamples(Property);
|
|
|
|
// Resize material to match output (area) size
|
|
FIntPoint OutputSize = FIntPoint((OutTextureSize.X * MaterialBox.GetSize().X) - (InGutter * 2), (OutTextureSize.Y * MaterialBox.GetSize().Y) - (InGutter * 2));
|
|
ConditionalImageResize(InputSize, OutputSize, InputSamples, false);
|
|
|
|
// Copy material data to the merged 'atlas' texture
|
|
FIntPoint OutputPosition = FIntPoint((OutTextureSize.X * MaterialBox.Min.X) + InGutter, (OutTextureSize.Y * MaterialBox.Min.Y) + InGutter);
|
|
CopyTextureRect(InputSamples.GetData(), OutputSize, OutSamples.GetData(), OutTextureSize, OutputPosition, bCopyOnlyMaskedPixels);
|
|
|
|
// Set the UV tranforms only once
|
|
if (Index == 0)
|
|
{
|
|
FUVOffsetScalePair& UVTransform = OutUVTransforms[MaterialIndex];
|
|
UVTransform.Key = MaterialBox.Min + (Gutter2D / FVector2D(OutTextureSize));
|
|
UVTransform.Value = MaterialBox.GetSize() - ((Gutter2D * 2.0f) / FVector2D(OutTextureSize));
|
|
}
|
|
|
|
bMaterialsWritten = true;
|
|
}
|
|
}
|
|
|
|
if (!bMaterialsWritten)
|
|
{
|
|
OutSamples.Empty();
|
|
OutMergedMaterial.SetPropertySize(Property, FIntPoint(0, 0));
|
|
}
|
|
else
|
|
{
|
|
// Smear borders
|
|
const FIntPoint PropertySize = OutMergedMaterial.GetPropertySize(Property);
|
|
FMaterialBakingHelpers::PerformUVBorderSmear(OutSamples, PropertySize.X, PropertySize.Y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
float FMeshMergeUtilities::FlattenEmissivescale(TArray<struct FFlattenMaterial>& InMaterialList) const
|
|
{
|
|
// Find maximum emissive scaling value across materials
|
|
float MaxScale = 0.0f;
|
|
for (const FFlattenMaterial& Material : InMaterialList)
|
|
{
|
|
MaxScale = FMath::Max(MaxScale, Material.EmissiveScale);
|
|
}
|
|
|
|
// Renormalize samples
|
|
const float Multiplier = 1.0f / MaxScale;
|
|
const int32 NumThreads = [&]()
|
|
{
|
|
return FPlatformProcess::SupportsMultithreading() ? FPlatformMisc::NumberOfCores() : 1;
|
|
}();
|
|
|
|
const int32 MaterialsPerThread = FMath::CeilToInt((float)InMaterialList.Num() / (float)NumThreads);
|
|
ParallelFor(NumThreads, [&InMaterialList, MaterialsPerThread, Multiplier, MaxScale]
|
|
(int32 Index)
|
|
{
|
|
int32 StartIndex = FMath::CeilToInt((float)Index * (float)MaterialsPerThread);
|
|
const int32 EndIndex = FMath::Min(FMath::CeilToInt((float)(Index + 1) * (float)MaterialsPerThread), InMaterialList.Num());
|
|
|
|
for (; StartIndex < EndIndex; ++StartIndex)
|
|
{
|
|
FFlattenMaterial& Material = InMaterialList[StartIndex];
|
|
if (Material.EmissiveScale != MaxScale)
|
|
{
|
|
for (FColor& Sample : Material.GetPropertySamples(EFlattenMaterialProperties::Emissive))
|
|
{
|
|
if (Sample != FColor::Magenta)
|
|
{
|
|
Sample.R = Sample.R * Multiplier;
|
|
Sample.G = Sample.G * Multiplier;
|
|
Sample.B = Sample.B * Multiplier;
|
|
Sample.A = Sample.A * Multiplier;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, NumThreads == 1);
|
|
|
|
return MaxScale;
|
|
}
|
|
|
|
static TArray<FVector2D> GetCustomTextureCoordinates(const FMeshDescription& InMeshDescription, const UStaticMesh* InStaticMesh, const FMeshProxySettings& InMeshProxySettings)
|
|
{
|
|
TArray<FVector2D> CustomTextureCoordinates;
|
|
|
|
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = FStaticMeshConstAttributes(InMeshDescription).GetVertexInstanceUVs();
|
|
|
|
// If we already have lightmap uvs generated and they are valid, we can reuse those instead of having to generate new ones
|
|
const int32 LightMapCoordinateIndex = InStaticMesh->GetLightMapCoordinateIndex();
|
|
if (InMeshProxySettings.bReuseMeshLightmapUVs &&
|
|
LightMapCoordinateIndex > 0 &&
|
|
VertexInstanceUVs.GetNumElements() > 0 &&
|
|
VertexInstanceUVs.GetNumChannels() > LightMapCoordinateIndex)
|
|
{
|
|
for (const FVertexInstanceID VertexInstanceID : InMeshDescription.VertexInstances().GetElementIDs())
|
|
{
|
|
CustomTextureCoordinates.Add(FVector2D(VertexInstanceUVs.Get(VertexInstanceID, LightMapCoordinateIndex)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FStaticMeshOperations::FGenerateUVOptions GenerateUVOptions;
|
|
GenerateUVOptions.TextureResolution = InMeshProxySettings.MaterialSettings.TextureSize.GetMax();
|
|
GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes = false;
|
|
GenerateUVOptions.UVMethod = GetUVGenerationMethodToUse();
|
|
|
|
bool bSuccess = FStaticMeshOperations::GenerateUV(InMeshDescription, GenerateUVOptions, CustomTextureCoordinates);
|
|
if (!bSuccess)
|
|
{
|
|
UE_LOG(LogMeshMerging, Warning, TEXT("GenerateUV: Failed to pack UVs for static mesh \"%s\" (num triangles = %d, texture resolution = %d)."), *InStaticMesh->GetName(), InMeshDescription.Triangles().Num(), InMeshProxySettings.MaterialSettings.TextureSize.GetMax());
|
|
CustomTextureCoordinates.Empty();
|
|
}
|
|
}
|
|
|
|
return CustomTextureCoordinates;
|
|
}
|
|
|
|
class FProxyMeshDescriptor
|
|
{
|
|
public:
|
|
FProxyMeshDescriptor(const UStaticMeshComponent* StaticMeshComponent, int32 LODIndex)
|
|
: LODIndex(LODIndex)
|
|
, LightMapIndex(INDEX_NONE)
|
|
{
|
|
ISMDescriptor.InitFrom(StaticMeshComponent, false);
|
|
|
|
// Retrieve lightmap for usage of lightmap data
|
|
if (StaticMeshComponent->LODData.IsValidIndex(0))
|
|
{
|
|
const FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[0];
|
|
const FMeshMapBuildData* MeshMapBuildData = StaticMeshComponent->GetMeshMapBuildData(ComponentLODInfo);
|
|
if (MeshMapBuildData)
|
|
{
|
|
LightMap = MeshMapBuildData->LightMap;
|
|
LightMapIndex = StaticMeshComponent->GetStaticMesh()->GetLightMapCoordinateIndex();
|
|
}
|
|
}
|
|
|
|
Hash = ISMDescriptor.ComputeHash();
|
|
Hash = FCrc::TypeCrc32(LODIndex, Hash);
|
|
|
|
if (LightMapIndex != INDEX_NONE)
|
|
{
|
|
Hash = FCrc::TypeCrc32(LightMap.GetReference(), Hash);
|
|
Hash = FCrc::TypeCrc32(LightMapIndex, Hash);
|
|
}
|
|
}
|
|
|
|
bool operator==(const FProxyMeshDescriptor& InOther) const
|
|
{
|
|
return Hash == InOther.Hash &&
|
|
LODIndex == InOther.LODIndex &&
|
|
LightMap == InOther.LightMap &&
|
|
LightMapIndex == InOther.LightMapIndex &&
|
|
ISMDescriptor == InOther.ISMDescriptor;
|
|
}
|
|
|
|
int32 GetLODIndex() const { return LODIndex; }
|
|
FLightMapRef GetLightMap() const { return LightMap; }
|
|
int32 GetLightMapIndex() const { return LightMapIndex; }
|
|
|
|
UStaticMesh* GetStaticMesh() const { return ISMDescriptor.StaticMesh; }
|
|
|
|
bool IsValid() const { return !MeshDescription.IsEmpty(); }
|
|
|
|
const FMeshDescription& GetMeshDescription() const
|
|
{
|
|
return MeshDescription;
|
|
}
|
|
|
|
const TArray<FVector2D>& GetCustomTextureCoordinates() const
|
|
{
|
|
return CustomTextureCoordinates;
|
|
}
|
|
|
|
void PrepareMeshDescription(const FMeshProxySettings& InMeshProxySettings)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(PrepareMeshDescription);
|
|
|
|
// Retrieve mesh data in FMeshDescription form
|
|
FStaticMeshAttributes(MeshDescription).Register();
|
|
FMeshMergeHelpers::RetrieveMesh(ISMDescriptor.StaticMesh, LODIndex, MeshDescription);
|
|
|
|
CustomTextureCoordinates = ::GetCustomTextureCoordinates(MeshDescription, ISMDescriptor.StaticMesh, InMeshProxySettings);
|
|
if (CustomTextureCoordinates.IsEmpty())
|
|
{
|
|
// Failure, clear mesh description
|
|
MeshDescription.Empty();
|
|
}
|
|
}
|
|
|
|
private:
|
|
int32 Hash;
|
|
|
|
int32 LODIndex;
|
|
FLightMapRef LightMap;
|
|
int32 LightMapIndex;
|
|
|
|
FISMComponentDescriptor ISMDescriptor;
|
|
|
|
FMeshDescription MeshDescription;
|
|
TArray<FVector2D> CustomTextureCoordinates;
|
|
};
|
|
|
|
static void ScaleTextureCoordinatesToBox(const FBox2D& Box, TArray<FVector2D>& InOutTextureCoordinates)
|
|
{
|
|
const FBox2D CoordinateBox(InOutTextureCoordinates);
|
|
const FVector2D CoordinateRange = CoordinateBox.GetSize();
|
|
const FVector2D Offset = CoordinateBox.Min + Box.Min;
|
|
const FVector2D Scale = Box.GetSize() / CoordinateRange;
|
|
for (FVector2D& Coordinate : InOutTextureCoordinates)
|
|
{
|
|
Coordinate = (Coordinate - Offset) * Scale;
|
|
}
|
|
}
|
|
|
|
typedef TFunctionRef<int32(const UStaticMeshComponent*)> FGetMeshLODFunc;
|
|
|
|
struct FInstancedMeshDescriptionData
|
|
{
|
|
FMeshDescription* MeshDescription;
|
|
TArray<FTransform> InstancesTransforms;
|
|
};
|
|
|
|
static TArray<FInstancedMeshDescriptionData> GatherGeometry(const TArray<UStaticMeshComponent*>& InStaticMeshComponents, const FMeshProxySettings& InMeshProxySettings, TArray<FProxyMeshDescriptor>& InDescriptors, const TArray<TArray<int32>>& InMeshesToMergePerDescriptor, const TArray<int32>& InMeshesToMeshDescriptor, FGetMeshLODFunc InGetMeshLODFunc, int32& OutSummedLightmapPixels)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::GatherGeometry);
|
|
|
|
TArray<FMeshDescription> TempDescriptionData;
|
|
TempDescriptionData.SetNum(InStaticMeshComponents.Num());
|
|
|
|
TAtomic<int32> SummedLightmapPixels(0);
|
|
|
|
TArray<FInstancedMeshDescriptionData> MeshesDescriptions;
|
|
MeshesDescriptions.SetNum(InDescriptors.Num());
|
|
|
|
// If grouping by identical meshes, prepare each mesh description along with their flattened UVs
|
|
// These meshes descriptions will serve for material baking, but also as the basis for creating a
|
|
// single merged mesh out of all instances.
|
|
if (InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UniqueUVs);
|
|
ParallelFor(InDescriptors.Num(), [&InDescriptors, &InMeshProxySettings](uint32 Index)
|
|
{
|
|
InDescriptors[Index].PrepareMeshDescription(InMeshProxySettings);
|
|
});
|
|
}
|
|
|
|
// Gather geometry from each component, expand ISMC geometry
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GatherGeometryFromComponents);
|
|
ParallelFor(InStaticMeshComponents.Num(), [InStaticMeshComponents, &InGetMeshLODFunc, InDescriptors, InMeshesToMeshDescriptor, InMeshProxySettings, &TempDescriptionData, &SummedLightmapPixels](uint32 Index)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GatherGeometryFromComponent);
|
|
const UStaticMeshComponent* StaticMeshComponent = InStaticMeshComponents[Index];
|
|
|
|
FMeshDescription& MeshDescription = TempDescriptionData[Index];
|
|
|
|
// Retrieve meshes
|
|
if (!InMeshProxySettings.bGroupIdenticalMeshesForBaking || !InDescriptors[InMeshesToMeshDescriptor[Index]].IsValid())
|
|
{
|
|
const int32 LODIndex = InGetMeshLODFunc(StaticMeshComponent);
|
|
static const bool bPropagateVertexColours = true;
|
|
|
|
// Retrieve mesh data in FMeshDescription form
|
|
FStaticMeshAttributes(MeshDescription).Register();
|
|
FMeshMergeHelpers::RetrieveMesh(StaticMeshComponent, LODIndex, MeshDescription, bPropagateVertexColours);
|
|
}
|
|
else
|
|
{
|
|
MeshDescription = InDescriptors[InMeshesToMeshDescriptor[Index]].GetMeshDescription();
|
|
|
|
if (!InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
FStaticMeshOperations::ApplyTransform(MeshDescription, StaticMeshComponent->GetComponentTransform());
|
|
}
|
|
}
|
|
|
|
// If the component is an ISMC then we need to duplicate the vertex data
|
|
int32 NumInstances = 1;
|
|
if (const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent))
|
|
{
|
|
if (!InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, MeshDescription);
|
|
}
|
|
|
|
NumInstances = InstancedStaticMeshComponent->PerInstanceSMData.Num();
|
|
}
|
|
|
|
int32 LightMapWidth, LightMapHeight;
|
|
StaticMeshComponent->GetLightMapResolution(LightMapWidth, LightMapHeight);
|
|
// Make sure we at least have some lightmap space allocated in case the static mesh is set up with invalid input
|
|
SummedLightmapPixels += FMath::Max(16, LightMapHeight * LightMapWidth * NumInstances);
|
|
}, EParallelForFlags::Unbalanced);
|
|
}
|
|
|
|
// For each mesh, append each component geometry
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(AppendMeshes);
|
|
FStaticMeshOperations::FAppendSettings AppendSettings;
|
|
for (int32 ChannelIdx = 0; ChannelIdx < FStaticMeshOperations::FAppendSettings::MAX_NUM_UV_CHANNELS; ++ChannelIdx)
|
|
{
|
|
AppendSettings.bMergeUVChannels[ChannelIdx] = true;
|
|
}
|
|
|
|
ParallelFor(InDescriptors.Num(), [&MeshesDescriptions, &InMeshesToMergePerDescriptor, &InStaticMeshComponents, &TempDescriptionData, &AppendSettings, &InMeshProxySettings](uint32 Index)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(AppendMeshes);
|
|
FMeshDescription* TargetMeshDescription = new FMeshDescription();
|
|
FStaticMeshAttributes(*TargetMeshDescription).Register();
|
|
|
|
// When using this option, do not expand the instances, but rather send their transforms to the ProxyLOD tool
|
|
if (InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
TArray<FTransform> InstancesTransforms;
|
|
|
|
for (int32 Idx : InMeshesToMergePerDescriptor[Index])
|
|
{
|
|
UStaticMeshComponent* StaticMeshComponent = InStaticMeshComponents[Idx];
|
|
|
|
if (InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
FTransform ComponentTransform = StaticMeshComponent->GetComponentTransform();
|
|
|
|
if (const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent))
|
|
{
|
|
for (const FInstancedStaticMeshInstanceData& InstanceData : InstancedStaticMeshComponent->PerInstanceSMData)
|
|
{
|
|
InstancesTransforms.Add(FTransform(InstanceData.Transform) * ComponentTransform);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InstancesTransforms.Add(ComponentTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!InMeshesToMergePerDescriptor[Index].IsEmpty())
|
|
{
|
|
*TargetMeshDescription = TempDescriptionData[InMeshesToMergePerDescriptor[Index][0]];
|
|
}
|
|
|
|
MeshesDescriptions[Index].InstancesTransforms = MoveTemp(InstancesTransforms);
|
|
}
|
|
else
|
|
{
|
|
TArray<const FMeshDescription*> SourceMeshDescriptions;
|
|
SourceMeshDescriptions.Reserve(InMeshesToMergePerDescriptor[Index].Num());
|
|
for (int32 TempIdx : InMeshesToMergePerDescriptor[Index])
|
|
{
|
|
SourceMeshDescriptions.Add(&TempDescriptionData[TempIdx]);
|
|
}
|
|
|
|
FStaticMeshOperations::AppendMeshDescriptions(SourceMeshDescriptions, *TargetMeshDescription, AppendSettings);
|
|
}
|
|
|
|
MeshesDescriptions[Index].MeshDescription = TargetMeshDescription;
|
|
}, EParallelForFlags::Unbalanced);
|
|
}
|
|
|
|
OutSummedLightmapPixels = SummedLightmapPixels;
|
|
|
|
return MeshesDescriptions;
|
|
}
|
|
|
|
TArray<FMeshData> PrepareBakingMeshes(const struct FMeshProxySettings& InMeshProxySettings, const TArray<FProxyMeshDescriptor>& InDescriptors, TArray<FInstancedMeshDescriptionData> InMeshDescriptionData)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(PrepareBakingMeshes)
|
|
|
|
check(InDescriptors.Num() == InMeshDescriptionData.Num());
|
|
|
|
TArray<FMeshData> MeshData;
|
|
MeshData.SetNum(InDescriptors.Num());
|
|
|
|
// GetLightMap() must be called from the game thread
|
|
check(IsInGameThread());
|
|
for (int32 MeshIndex = 0; MeshIndex < InDescriptors.Num(); ++MeshIndex)
|
|
{
|
|
if (InDescriptors[MeshIndex].GetLightMapIndex() != INDEX_NONE)
|
|
{
|
|
MeshData[MeshIndex].LightMap = InDescriptors[MeshIndex].GetLightMap();
|
|
MeshData[MeshIndex].LightMapIndex = InDescriptors[MeshIndex].GetLightMapIndex();
|
|
}
|
|
}
|
|
|
|
// Parallel step - fetching custom (unwrapped) texture coordinates is the slowest part here
|
|
ParallelFor(InDescriptors.Num(), [&MeshData, &InDescriptors, &InMeshDescriptionData, &InMeshProxySettings](uint32 MeshIndex)
|
|
{
|
|
const FProxyMeshDescriptor& MeshDescriptor = InDescriptors[MeshIndex];
|
|
|
|
FMeshData& MeshSettings = MeshData[MeshIndex];
|
|
MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
|
|
if (InMeshProxySettings.bGroupIdenticalMeshesForBaking)
|
|
{
|
|
// Grouping by identical meshes, the UVs should have already been setup
|
|
MeshSettings.MeshDescription = &MeshDescriptor.GetMeshDescription();
|
|
MeshSettings.CustomTextureCoordinates = MeshDescriptor.GetCustomTextureCoordinates();
|
|
}
|
|
else
|
|
{
|
|
FMeshDescription& MeshDescription = *InMeshDescriptionData[MeshIndex].MeshDescription;
|
|
MeshSettings.MeshDescription = &MeshDescription;
|
|
MeshSettings.CustomTextureCoordinates = GetCustomTextureCoordinates(MeshDescription, MeshDescriptor.GetStaticMesh(), InMeshProxySettings);
|
|
}
|
|
|
|
if (MeshSettings.CustomTextureCoordinates.IsEmpty())
|
|
{
|
|
MeshSettings.MeshDescription = nullptr;
|
|
MeshSettings.TextureCoordinateIndex = 0;
|
|
}
|
|
});
|
|
|
|
return MeshData;
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, const FCreateProxyDelegate& InProxyCreatedDelegate, const bool bAllowAsync, const float ScreenSize) const
|
|
{
|
|
CreateProxyMesh(InActors, InMeshProxySettings, GEngine->DefaultFlattenMaterial, InOuter, InProxyBasePackageName, InGuid, InProxyCreatedDelegate, bAllowAsync, ScreenSize);
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateProxyMesh(const TArray<UStaticMeshComponent*>& InStaticMeshComps, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, const FCreateProxyDelegate& InProxyCreatedDelegate, const bool bAllowAsync, const float ScreenSize) const
|
|
{
|
|
CreateProxyMesh(InStaticMeshComps, InMeshProxySettings, GEngine->DefaultFlattenMaterial, InOuter, InProxyBasePackageName, InGuid, InProxyCreatedDelegate, bAllowAsync, ScreenSize);
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UMaterialInterface* InBaseMaterial, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, const FCreateProxyDelegate& InProxyCreatedDelegate, const bool bAllowAsync /*= false*/, const float ScreenSize /*= 1.0f*/) const
|
|
{
|
|
// No actors given as input
|
|
if (InActors.Num() == 0)
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("No actors specified to generate a proxy mesh for"));
|
|
return;
|
|
}
|
|
|
|
// Collect components to merge
|
|
TArray<UStaticMeshComponent*> ComponentsToMerge;
|
|
for (AActor* Actor : InActors)
|
|
{
|
|
TInlineComponentArray<UStaticMeshComponent*> Components;
|
|
Actor->GetComponents(Components);
|
|
ComponentsToMerge.Append(Components);
|
|
}
|
|
|
|
CreateProxyMesh(ComponentsToMerge, InMeshProxySettings, InBaseMaterial, InOuter, InProxyBasePackageName, InGuid, InProxyCreatedDelegate, bAllowAsync, ScreenSize);
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateProxyMesh(const TArray<UStaticMeshComponent*>& InComponentsToMerge, const struct FMeshProxySettings& InMeshProxySettings, UMaterialInterface* InBaseMaterial,
|
|
UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, const FCreateProxyDelegate& InProxyCreatedDelegate, const bool bAllowAsync, const float ScreenSize) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::CreateProxyMesh)
|
|
|
|
// The MeshReductionInterface manages the choice mesh reduction plugins, Unreal native vs third party (e.g. Simplygon)
|
|
IMeshReductionModule& ReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshReductionModule>("MeshReductionInterface");
|
|
|
|
// Error/warning checking for input
|
|
if (ReductionModule.GetMeshMergingInterface() == nullptr)
|
|
{
|
|
UE_LOG(LogMeshMerging, Error, TEXT("No mesh reduction module available. You must enable a plugin that provides that functionality (ex: ProxyLODPlugin)"));
|
|
return;
|
|
}
|
|
|
|
// Check that the delegate has a func-ptr bound to it
|
|
if (!InProxyCreatedDelegate.IsBound())
|
|
{
|
|
UE_LOG(LogMeshMerging, Warning, TEXT("Invalid (unbound) delegate for returning generated proxy mesh"));
|
|
return;
|
|
}
|
|
|
|
TArray<UStaticMeshComponent*> ComponentsToMerge = InComponentsToMerge;
|
|
|
|
// Remove invalid components
|
|
ComponentsToMerge.RemoveAll([](UStaticMeshComponent* Val) { return Val->GetStaticMesh() == nullptr; });
|
|
|
|
// No actors given as input
|
|
if (ComponentsToMerge.Num() == 0)
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("No static mesh specified to generate a proxy mesh for"));
|
|
|
|
TArray<UObject*> OutAssetsToSync;
|
|
InProxyCreatedDelegate.ExecuteIfBound(InGuid, OutAssetsToSync);
|
|
|
|
return;
|
|
}
|
|
|
|
// Base asset name for a new assets
|
|
// In case outer is null ProxyBasePackageName has to be long package name
|
|
if (InOuter == nullptr && FPackageName::IsShortPackageName(InProxyBasePackageName))
|
|
{
|
|
UE_LOG(LogMeshMerging, Warning, TEXT("Invalid long package name: '%s'."), *InProxyBasePackageName);
|
|
return;
|
|
}
|
|
|
|
FScopedSlowTask SlowTask(100.f, (LOCTEXT("CreateProxyMesh_CreateMesh", "Creating Mesh Proxy")));
|
|
SlowTask.MakeDialog();
|
|
|
|
TArray<FRawMeshExt> SourceMeshes;
|
|
TMap<FMeshIdAndLOD, TArray<int32>> GlobalMaterialMap;
|
|
|
|
FBoxSphereBounds::Builder EstimatedBoundsBuilder;
|
|
for (const UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
|
|
{
|
|
EstimatedBoundsBuilder += StaticMeshComponent->Bounds;
|
|
}
|
|
FBoxSphereBounds EstimatedBounds = EstimatedBoundsBuilder;
|
|
|
|
static const float FOVRad = FMath::DegreesToRadians(45.0f);
|
|
static const FMatrix ProjectionMatrix = FPerspectiveMatrix(FOVRad, 1920, 1080, 0.01f);
|
|
FHierarchicalLODUtilitiesModule& HLODModule = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
|
|
IHierarchicalLODUtilities* Utilities = HLODModule.GetUtilities();
|
|
float EstimatedDistance = Utilities->CalculateDrawDistanceFromScreenSize(EstimatedBounds.SphereRadius, ScreenSize, ProjectionMatrix);
|
|
|
|
auto SelectLODFunc = [&InMeshProxySettings, &Utilities, EstimatedDistance] (const UStaticMeshComponent* Component)
|
|
{
|
|
int32 LODIndex = 0;
|
|
if (InMeshProxySettings.bCalculateCorrectLODModel)
|
|
{
|
|
LODIndex = Utilities->GetLODLevelForScreenSize(Component, Utilities->CalculateScreenSizeFromDrawDistance(Component->Bounds.SphereRadius, ProjectionMatrix, EstimatedDistance));
|
|
}
|
|
return LODIndex;
|
|
};
|
|
|
|
SlowTask.EnterProgressFrame(5.0f, LOCTEXT("CreateProxyMesh_CollectingMeshes", "Collecting Input Static Meshes"));
|
|
|
|
// Mesh / LOD index
|
|
TMap<uint32, FMeshDescription*> RawMeshLODs;
|
|
|
|
// Mesh index, <original section index, unique section index>
|
|
TMultiMap<uint32, TPair<uint32, uint32>> MeshSectionToUniqueSection;
|
|
|
|
// Unique set of sections in mesh
|
|
TArray<FSectionInfo> UniqueSections;
|
|
TMultiMap<uint32, uint32> SectionToMesh;
|
|
|
|
TArray<const UStaticMeshComponent*> ImposterMeshComponents;
|
|
TArray<UStaticMeshComponent*> StaticMeshComponents;
|
|
for (UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
|
|
{
|
|
if (StaticMeshComponent->HLODBatchingPolicy != EHLODBatchingPolicy::None)
|
|
{
|
|
ImposterMeshComponents.Add(StaticMeshComponent);
|
|
}
|
|
else
|
|
{
|
|
StaticMeshComponents.Add(StaticMeshComponent);
|
|
}
|
|
}
|
|
|
|
TArray<FProxyMeshDescriptor> MeshDescriptors;
|
|
TArray<TArray<int32>> MeshesToMergePerDescriptor;
|
|
TArray<int32> MeshToMeshDescriptor;
|
|
|
|
TArray<TArray<FSectionInfo>> GlobalSections;
|
|
|
|
MeshToMeshDescriptor.Reserve(StaticMeshComponents.Num());
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < StaticMeshComponents.Num(); ++ComponentIndex)
|
|
{
|
|
const UStaticMeshComponent* StaticMeshComponent = StaticMeshComponents[ComponentIndex];
|
|
|
|
FProxyMeshDescriptor MeshDescriptor(StaticMeshComponent, SelectLODFunc(StaticMeshComponent));
|
|
|
|
int32 Index;
|
|
if (!InMeshProxySettings.bGroupIdenticalMeshesForBaking || !MeshDescriptors.Find(MeshDescriptor, Index))
|
|
{
|
|
Index = MeshDescriptors.Num();
|
|
MeshDescriptors.Add(MeshDescriptor);
|
|
MeshesToMergePerDescriptor.AddDefaulted();
|
|
|
|
TArray<FSectionInfo>& Sections = GlobalSections.AddDefaulted_GetRef();
|
|
|
|
// Extract sections for given LOD index from the mesh
|
|
FMeshMergeHelpers::ExtractSections(StaticMeshComponent, MeshDescriptor.GetLODIndex(), Sections);
|
|
}
|
|
|
|
MeshesToMergePerDescriptor[Index].Add(ComponentIndex);
|
|
MeshToMeshDescriptor.Add(Index);
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < GlobalSections.Num(); ++MeshIndex)
|
|
{
|
|
TArray<FSectionInfo>& Sections = GlobalSections[MeshIndex];
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = Sections[SectionIndex];
|
|
|
|
const int32 UniqueIndex = UniqueSections.AddUnique(Section);
|
|
MeshSectionToUniqueSection.Add(MeshIndex, TPair<uint32, uint32>(SectionIndex, UniqueIndex));
|
|
SectionToMesh.Add(UniqueIndex, MeshIndex);
|
|
}
|
|
}
|
|
|
|
int32 SummedLightmapPixels;
|
|
|
|
TArray<FInstancedMeshDescriptionData> MeshDescriptionData = GatherGeometry(StaticMeshComponents, InMeshProxySettings, MeshDescriptors, MeshesToMergePerDescriptor, MeshToMeshDescriptor, SelectLODFunc, SummedLightmapPixels);
|
|
TArray<FMeshData> MeshBakingData = PrepareBakingMeshes(InMeshProxySettings, MeshDescriptors, MeshDescriptionData);
|
|
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
//Unique material index to unique section index
|
|
TMultiMap<uint32, uint32> MaterialToSectionMap;
|
|
for (int32 SectionIndex = 0; SectionIndex < UniqueSections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = UniqueSections[SectionIndex];
|
|
const int32 UniqueIndex = UniqueMaterials.AddUnique(Section.Material);
|
|
MaterialToSectionMap.Add(UniqueIndex, SectionIndex);
|
|
}
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
|
|
FMaterialProxySettings MaterialProxySettings = InMeshProxySettings.MaterialSettings;
|
|
TArray<UPrimitiveComponent*> PrimitiveComponents;
|
|
Algo::Transform(StaticMeshComponents, PrimitiveComponents, [](UStaticMeshComponent* SMComponent) { return SMComponent; });
|
|
if (MaterialProxySettings.ResolveTexelDensity(PrimitiveComponents))
|
|
{
|
|
double Total3DArea = 0;
|
|
|
|
for (const FInstancedMeshDescriptionData& InstancedMeshDescriptionData : MeshDescriptionData)
|
|
{
|
|
double Mesh3DArea = 0;
|
|
|
|
const FMeshDescription& MeshDescription = *InstancedMeshDescriptionData.MeshDescription;
|
|
|
|
FStaticMeshConstAttributes Attributes(MeshDescription);
|
|
TVertexAttributesConstRef<FVector3f> Positions = Attributes.GetVertexPositions();
|
|
|
|
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
|
|
{
|
|
// World space area
|
|
TArrayView<const FVertexID> TriVertices = MeshDescription.GetTriangleVertices(TriangleID);
|
|
Mesh3DArea += UE::Geometry::VectorUtil::Area(Positions[TriVertices[0]], Positions[TriVertices[1]], Positions[TriVertices[2]]);
|
|
}
|
|
|
|
// Account for multiple instances (no transforms means a single instance)
|
|
uint32 NumInstances = FMath::Max(1, InstancedMeshDescriptionData.InstancesTransforms.Num());
|
|
Total3DArea += Mesh3DArea * NumInstances;
|
|
}
|
|
|
|
MaterialProxySettings.TextureSize = FMaterialUtilities::GetTextureSizeFromTargetTexelDensity(Total3DArea, 1.0f, MaterialProxySettings.TargetTexelDensityPerMeter);
|
|
MaterialProxySettings.TextureSizingType = ETextureSizingType::TextureSizingType_UseSingleTextureSize;
|
|
}
|
|
|
|
UMaterialOptions* Options = PopulateMaterialOptions(MaterialProxySettings);
|
|
TGCObjectScopeGuard<UMaterialOptions> MaterialOptionsGCScopeGuard(Options);
|
|
|
|
TArray<EMaterialProperty> MaterialProperties;
|
|
for (const FPropertyEntry& Entry : Options->Properties)
|
|
{
|
|
if (Entry.Property != MP_MAX)
|
|
{
|
|
MaterialProperties.Add(Entry.Property);
|
|
}
|
|
}
|
|
|
|
// Mesh index / ( Mesh relative section index / output index )
|
|
TMultiMap<uint32, TPair<uint32, uint32>> OutputMaterialsMap;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::MaterialAnalysisAndUVGathering);
|
|
|
|
for (int32 MaterialIndex = 0; MaterialIndex < UniqueMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
UMaterialInterface* Material = UniqueMaterials[MaterialIndex];
|
|
|
|
//Unique section indices
|
|
TArray<uint32> SectionIndices;
|
|
MaterialToSectionMap.MultiFind(MaterialIndex, SectionIndices);
|
|
|
|
// Check whether or not this material requires mesh data
|
|
int32 NumTexCoords = 0;
|
|
bool bUseVertexData = false;
|
|
FMaterialUtilities::AnalyzeMaterial(Material, MaterialProperties, NumTexCoords, bUseVertexData);
|
|
|
|
FMaterialData MaterialSettings;
|
|
MaterialSettings.Material = Material;
|
|
|
|
for (const FPropertyEntry& Entry : Options->Properties)
|
|
{
|
|
if (!Entry.bUseConstantValue && Material->IsPropertyActive(Entry.Property) && Entry.Property != MP_MAX)
|
|
{
|
|
MaterialSettings.PropertySizes.Add(Entry.Property, Entry.bUseCustomSize ? Entry.CustomSize : Options->TextureSize);
|
|
}
|
|
}
|
|
|
|
if (bUseVertexData || NumTexCoords != 0)
|
|
{
|
|
for (uint32 SectionIndex : SectionIndices)
|
|
{
|
|
TArray<uint32> MeshIndices;
|
|
SectionToMesh.MultiFind(SectionIndex, MeshIndices);
|
|
|
|
for (const uint32 MeshIndex : MeshIndices)
|
|
{
|
|
FMeshData MeshSettings = MeshBakingData[MeshIndex];
|
|
|
|
// Section index is a unique one so we need to map it to the mesh's equivalent(s)
|
|
TArray<TPair<uint32, uint32>> SectionToUniqueSectionIndices;
|
|
MeshSectionToUniqueSection.MultiFind(MeshIndex, SectionToUniqueSectionIndices);
|
|
for (const TPair<uint32, uint32>& IndexPair : SectionToUniqueSectionIndices)
|
|
{
|
|
if (IndexPair.Value == SectionIndex)
|
|
{
|
|
MeshSettings.MaterialIndices.Add(IndexPair.Key);
|
|
OutputMaterialsMap.Add(MeshIndex, TPair<uint32, uint32>(IndexPair.Key, GlobalMeshSettings.Num()));
|
|
}
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MoveTemp(MeshSettings));
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add simple bake entry
|
|
FMeshData MeshSettings;
|
|
MeshSettings.MeshDescription = nullptr;
|
|
MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
MeshSettings.TextureCoordinateIndex = 0;
|
|
|
|
// For each original material index add an entry to the corresponding LOD and bake output index
|
|
for (uint32 SectionIndex : SectionIndices)
|
|
{
|
|
TArray<uint32> MeshIndices;
|
|
SectionToMesh.MultiFind(SectionIndex, MeshIndices);
|
|
|
|
for (uint32 MeshIndex : MeshIndices)
|
|
{
|
|
TArray<TPair<uint32, uint32>> SectionToUniqueSectionIndices;
|
|
MeshSectionToUniqueSection.MultiFind(MeshIndex, SectionToUniqueSectionIndices);
|
|
for (const TPair<uint32, uint32>& IndexPair : SectionToUniqueSectionIndices)
|
|
{
|
|
if (IndexPair.Value == SectionIndex)
|
|
{
|
|
OutputMaterialsMap.Add(MeshIndex, TPair<uint32, uint32>(IndexPair.Key, GlobalMeshSettings.Num()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MeshSettings);
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FFlattenMaterial> FlattenedMaterials;
|
|
IMaterialBakingModule& MaterialBakingModule = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
|
|
auto MaterialFlattenLambda =
|
|
[this, &Options, &GlobalMeshSettings, &GlobalMaterialSettings, &MeshDescriptionData, &OutputMaterialsMap, &MaterialBakingModule](TArray<FFlattenMaterial>& FlattenedMaterialArray)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(MaterialFlatten)
|
|
|
|
TArray<FMeshData*> MeshSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMeshSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MeshSettingPtrs.Add(&GlobalMeshSettings[SettingsIndex]);
|
|
}
|
|
|
|
TArray<FMaterialData*> MaterialSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMaterialSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MaterialSettingPtrs.Add(&GlobalMaterialSettings[SettingsIndex]);
|
|
}
|
|
|
|
// This scope ensures BakeOutputs is never used after TransferOutputToFlatMaterials
|
|
{
|
|
TArray<FBakeOutput> BakeOutputs;
|
|
MaterialBakingModule.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs);
|
|
|
|
// Append constant properties ?
|
|
TArray<FColor> ConstantData;
|
|
FIntPoint ConstantSize(1, 1);
|
|
for (const FPropertyEntry& Entry : Options->Properties)
|
|
{
|
|
if (Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
ConstantData.SetNum(1, EAllowShrinking::No);
|
|
ConstantData[0] = FColor(Entry.ConstantValue * 255.0f, Entry.ConstantValue * 255.0f, Entry.ConstantValue * 255.0f);
|
|
for (FBakeOutput& Output : BakeOutputs)
|
|
{
|
|
Output.PropertyData.Add(Entry.Property, ConstantData);
|
|
Output.PropertySizes.Add(Entry.Property, ConstantSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TransferOutputToFlatMaterials(GlobalMaterialSettings, BakeOutputs, FlattenedMaterialArray);
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(RemapBakedMaterials)
|
|
|
|
// Now have the baked out material data, need to have a map or actually remap the raw mesh data to baked material indices
|
|
for (int32 MeshIndex = 0; MeshIndex < MeshDescriptionData.Num(); ++MeshIndex)
|
|
{
|
|
FMeshDescription& MeshDescription = *MeshDescriptionData[MeshIndex].MeshDescription;
|
|
|
|
TArray<TPair<uint32, uint32>> SectionAndOutputIndices;
|
|
OutputMaterialsMap.MultiFind(MeshIndex, SectionAndOutputIndices);
|
|
TArray<int32> Remap;
|
|
// Reorder loops
|
|
for (const TPair<uint32, uint32>& IndexPair : SectionAndOutputIndices)
|
|
{
|
|
const int32 SectionIndex = IndexPair.Key;
|
|
const int32 NewIndex = IndexPair.Value;
|
|
|
|
if (Remap.Num() < (SectionIndex + 1))
|
|
{
|
|
Remap.SetNum(SectionIndex + 1);
|
|
}
|
|
|
|
Remap[SectionIndex] = NewIndex;
|
|
}
|
|
|
|
TMap<FPolygonGroupID, FPolygonGroupID> RemapPolygonGroup;
|
|
for (const FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
|
|
{
|
|
checkf(Remap.IsValidIndex(PolygonGroupID.GetValue()), TEXT("Missing material bake output index entry for mesh(section)"));
|
|
int32 RemapID = Remap[PolygonGroupID.GetValue()];
|
|
RemapPolygonGroup.Add(PolygonGroupID, FPolygonGroupID(RemapID));
|
|
}
|
|
MeshDescription.RemapPolygonGroups(RemapPolygonGroup);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Landscape culling. NB these are temporary copies of the culling data and should be deleted after use.
|
|
TArray<FMeshDescription*> CullingRawMeshes;
|
|
if (InMeshProxySettings.bUseLandscapeCulling)
|
|
{
|
|
SlowTask.EnterProgressFrame(5.0f, LOCTEXT("CreateProxyMesh_LandscapeCulling", "Applying Landscape Culling"));
|
|
UWorld* InWorld = ComponentsToMerge[0]->GetWorld();
|
|
FMeshMergeHelpers::RetrieveCullingLandscapeAndVolumes(InWorld, EstimatedBounds, InMeshProxySettings.LandscapeCullingPrecision, CullingRawMeshes);
|
|
}
|
|
|
|
// Allocate merge complete data
|
|
FMergeCompleteData* Data = new FMergeCompleteData();
|
|
Data->InOuter = InOuter;
|
|
Data->InProxySettings = InMeshProxySettings;
|
|
Data->ProxyBasePackageName = InProxyBasePackageName;
|
|
Data->CallbackDelegate = InProxyCreatedDelegate;
|
|
Data->ImposterComponents = ImposterMeshComponents;
|
|
Data->StaticMeshComponents = StaticMeshComponents;
|
|
Data->BaseMaterial = InBaseMaterial;
|
|
|
|
// Lightmap resolution
|
|
if (InMeshProxySettings.bComputeLightMapResolution)
|
|
{
|
|
Data->InProxySettings.LightMapResolution = FMath::CeilToInt(FMath::Sqrt(static_cast<float>(SummedLightmapPixels)));
|
|
}
|
|
|
|
// Add this proxy job to map
|
|
Processor->AddProxyJob(InGuid, Data);
|
|
|
|
TArray<FInstancedMeshMergeData> MergeDataEntries;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(MergeDataPreparation)
|
|
|
|
for (int32 Index = 0; Index < MeshDescriptionData.Num(); ++Index)
|
|
{
|
|
FInstancedMeshMergeData MergeData;
|
|
MergeData.SourceStaticMesh = MeshDescriptors[Index].GetStaticMesh();
|
|
MergeData.RawMesh = MeshDescriptionData[Index].MeshDescription;
|
|
MergeData.NewUVs = MeshDescriptors[Index].GetCustomTextureCoordinates();
|
|
MergeData.bIsClippingMesh = false;
|
|
MergeData.InstanceTransforms = MeshDescriptionData[Index].InstancesTransforms;
|
|
|
|
FMeshMergeHelpers::CalculateTextureCoordinateBoundsForMesh(*MergeData.RawMesh, MergeData.TexCoordBounds);
|
|
|
|
if (MergeData.NewUVs.IsEmpty())
|
|
{
|
|
FMeshData* MeshData = GlobalMeshSettings.FindByPredicate([&](const FMeshData& Entry)
|
|
{
|
|
return Entry.MeshDescription == MergeData.RawMesh && (Entry.CustomTextureCoordinates.Num() || Entry.TextureCoordinateIndex != 0);
|
|
});
|
|
|
|
if (MeshData)
|
|
{
|
|
if (MeshData->CustomTextureCoordinates.Num())
|
|
{
|
|
MergeData.NewUVs = MeshData->CustomTextureCoordinates;
|
|
}
|
|
else
|
|
{
|
|
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = FStaticMeshConstAttributes(*MeshData->MeshDescription).GetVertexInstanceUVs();
|
|
MergeData.NewUVs.Reset(MeshData->MeshDescription->VertexInstances().Num());
|
|
for (const FVertexInstanceID VertexInstanceID : MeshData->MeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
MergeData.NewUVs.Add(FVector2D(VertexInstanceUVs.Get(VertexInstanceID, MeshData->TextureCoordinateIndex)));
|
|
}
|
|
}
|
|
MergeData.TexCoordBounds[0] = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
}
|
|
}
|
|
MergeDataEntries.Add(MergeData);
|
|
}
|
|
}
|
|
|
|
if (MergeDataEntries.Num() != 0)
|
|
{
|
|
// Populate landscape clipping geometry
|
|
for (FMeshDescription* RawMesh : CullingRawMeshes)
|
|
{
|
|
FInstancedMeshMergeData ClipData;
|
|
ClipData.bIsClippingMesh = true;
|
|
ClipData.RawMesh = RawMesh;
|
|
MergeDataEntries.Add(ClipData);
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(50.0f, LOCTEXT("CreateProxyMesh_GenerateProxy", "Generating Proxy Mesh"));
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ProxyGeneration)
|
|
|
|
// Choose Simplygon Swarm (if available) or local proxy lod method
|
|
if (ReductionModule.GetDistributedMeshMergingInterface() != nullptr && GetDefault<UEditorPerProjectUserSettings>()->bUseSimplygonSwarm && bAllowAsync)
|
|
{
|
|
MaterialFlattenLambda(FlattenedMaterials);
|
|
|
|
ReductionModule.GetDistributedMeshMergingInterface()->ProxyLOD(MergeDataEntries, Data->InProxySettings, FlattenedMaterials, InGuid);
|
|
}
|
|
else
|
|
{
|
|
IMeshMerging* MeshMerging = ReductionModule.GetMeshMergingInterface();
|
|
|
|
// Register the Material Flattening code if parallel execution is supported, otherwise directly run it.
|
|
|
|
if (MeshMerging->bSupportsParallelMaterialBake())
|
|
{
|
|
MeshMerging->BakeMaterialsDelegate.BindLambda(MaterialFlattenLambda);
|
|
}
|
|
else
|
|
{
|
|
MaterialFlattenLambda(FlattenedMaterials);
|
|
}
|
|
|
|
MeshMerging->ProxyLOD(MergeDataEntries, Data->InProxySettings, FlattenedMaterials, InGuid);
|
|
|
|
|
|
Processor->Tick(0); // make sure caller gets merging results
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FMeshDescription MeshDescription;
|
|
FStaticMeshAttributes(MeshDescription).Register();
|
|
FFlattenMaterial FlattenMaterial;
|
|
Processor->ProxyGenerationComplete(MeshDescription, FlattenMaterial, InGuid);
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Cleanup)
|
|
|
|
// Clean up the CullingRawMeshes
|
|
ParallelFor(CullingRawMeshes.Num(),
|
|
[&CullingRawMeshes](int32 Index)
|
|
{
|
|
delete CullingRawMeshes[Index];
|
|
}
|
|
);
|
|
|
|
// Clean up the MeshDescriptionData
|
|
ParallelFor(
|
|
MeshDescriptionData.Num(),
|
|
[&MeshDescriptionData](int32 Index)
|
|
{
|
|
delete MeshDescriptionData[Index].MeshDescription;
|
|
}
|
|
);
|
|
}
|
|
|
|
void FMeshMergeUtilities::RetrieveMeshDescription(const UStaticMeshComponent* InStaticMeshComponent, int32 LODIndex, FMeshDescription& InOutMeshDescription, bool bPropagateMeshData) const
|
|
{
|
|
FMeshMergeHelpers::RetrieveMesh(InStaticMeshComponent, LODIndex, InOutMeshDescription, bPropagateMeshData);
|
|
}
|
|
|
|
void FMeshMergeUtilities::RetrieveMeshDescription(const USkeletalMeshComponent* InSkeletalMeshComponent, int32 LODIndex, FMeshDescription& InOutMeshDescription, bool bPropagateMeshData) const
|
|
{
|
|
FMeshMergeHelpers::RetrieveMesh(InSkeletalMeshComponent, LODIndex, InOutMeshDescription, bPropagateMeshData);
|
|
}
|
|
|
|
void FMeshMergeUtilities::RetrieveMeshDescription(const UStaticMesh* InStaticMesh, int32 LODIndex, FMeshDescription& InOutMeshDescription) const
|
|
{
|
|
FMeshMergeHelpers::RetrieveMesh(InStaticMesh, LODIndex, InOutMeshDescription);
|
|
}
|
|
|
|
void FMeshMergeUtilities::RegisterExtension(IMeshMergeExtension* InExtension)
|
|
{
|
|
MeshMergeExtensions.Add(InExtension);
|
|
}
|
|
|
|
void FMeshMergeUtilities::UnregisterExtension(IMeshMergeExtension* InExtension)
|
|
{
|
|
MeshMergeExtensions.Remove(InExtension);
|
|
}
|
|
|
|
bool RetrieveRawMeshData(FMeshMergeDataTracker& DataTracker
|
|
, const int32 ComponentIndex
|
|
, const int32 LODIndex
|
|
, UStaticMeshComponent* Component
|
|
, const bool bPropagateMeshData
|
|
, TArray<FSectionInfo>& Sections
|
|
, FStaticMeshComponentAdapter& Adapter
|
|
, const bool bMergeMaterialData
|
|
, const FMeshMergingSettings& InSettings)
|
|
{
|
|
// Retrieve raw mesh data
|
|
FMeshDescription& RawMesh = DataTracker.AddAndRetrieveRawMesh(ComponentIndex, LODIndex, Component->GetStaticMesh());
|
|
Adapter.RetrieveRawMeshData(LODIndex, RawMesh, bPropagateMeshData);
|
|
|
|
// Reset section for reuse
|
|
Sections.SetNum(0, EAllowShrinking::No);
|
|
|
|
// Extract sections for given LOD index from the mesh
|
|
Adapter.RetrieveMeshSections(LODIndex, Sections);
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
const FSectionInfo& Section = Sections[SectionIndex];
|
|
// Unique section index for remapping
|
|
const int32 UniqueIndex = DataTracker.AddSection(Section);
|
|
|
|
// Store of original to unique section index entry for this component + LOD index
|
|
DataTracker.AddSectionRemapping(ComponentIndex, LODIndex, SectionIndex, UniqueIndex);
|
|
DataTracker.AddMaterialSlotName(Section.Material, Section.MaterialSlotName);
|
|
|
|
if (!bMergeMaterialData)
|
|
{
|
|
FStaticMeshOperations::SwapPolygonPolygonGroup(RawMesh, UniqueIndex, Section.StartIndex, Section.EndIndex, false);
|
|
}
|
|
}
|
|
|
|
//Compact the PolygonGroupID to make sure it follow the section index
|
|
FElementIDRemappings RemapInformation;
|
|
RawMesh.Compact(RemapInformation);
|
|
|
|
// If the component is an ISMC then we need to duplicate the vertex data
|
|
if (Component->IsA<UInstancedStaticMeshComponent>())
|
|
{
|
|
const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(Component);
|
|
FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, RawMesh);
|
|
}
|
|
|
|
if (InSettings.bUseLandscapeCulling)
|
|
{
|
|
FMeshMergeHelpers::CullTrianglesFromVolumesAndUnderLandscapes(Component->GetWorld(), Adapter.GetBounds(), RawMesh);
|
|
}
|
|
|
|
// If the valid became invalid during retrieval remove it again
|
|
const bool bValidMesh = RawMesh.VertexInstances().Num() > 0;
|
|
if (!bValidMesh)
|
|
{
|
|
DataTracker.RemoveRawMesh(ComponentIndex, LODIndex);
|
|
}
|
|
else if (Component->GetStaticMesh() != nullptr)
|
|
{
|
|
// If the mesh is valid at this point, record the lightmap UV so we have a record for use later
|
|
DataTracker.AddLightmapChannelRecord(ComponentIndex, LODIndex, Component->GetStaticMesh()->GetLightMapCoordinateIndex());
|
|
}
|
|
return bValidMesh;
|
|
}
|
|
|
|
void FMeshMergeUtilities::MergeComponentsToStaticMesh(const TArray<UPrimitiveComponent*>& ComponentsToMerge, UWorld* World, const FMeshMergingSettings& InSettings, UMaterialInterface* InBaseMaterial, UPackage* InOuter, const FString& InBasePackageName, TArray<UObject*>& OutAssetsToSync, FVector& OutMergedActorLocation, const float ScreenSize, bool bSilent /*= false*/) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::MergeComponentsToStaticMesh);
|
|
|
|
// Use first mesh for naming and pivot
|
|
bool bFirstMesh = true;
|
|
FString MergedAssetPackageName;
|
|
FVector MergedAssetPivot;
|
|
|
|
TArray<UStaticMeshComponent*> StaticMeshComponentsToMerge;
|
|
TArray<const UStaticMeshComponent*> ImposterComponents;
|
|
|
|
for (int32 MeshId = 0; MeshId < ComponentsToMerge.Num(); ++MeshId)
|
|
{
|
|
UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(ComponentsToMerge[MeshId]);
|
|
if (MeshComponent && MeshComponent->GetStaticMesh())
|
|
{
|
|
// Make sure referenced lightmaps and shadowmaps are compiled
|
|
if (MeshComponent->LODData.IsValidIndex(0))
|
|
{
|
|
const FStaticMeshComponentLODInfo& ComponentLODInfo = MeshComponent->LODData[0];
|
|
const FMeshMapBuildData* MeshMapBuildData = MeshComponent->GetMeshMapBuildData(ComponentLODInfo);
|
|
if (MeshMapBuildData)
|
|
{
|
|
TArray<UTexture2D*> ReferencedTextures;
|
|
|
|
FLightMap2D* Lightmap = MeshMapBuildData && MeshMapBuildData->LightMap ? MeshMapBuildData->LightMap->GetLightMap2D() : nullptr;
|
|
if (Lightmap)
|
|
{
|
|
Lightmap->GetReferencedTextures(ReferencedTextures);
|
|
}
|
|
|
|
FShadowMap2D* Shadowmap = MeshMapBuildData && MeshMapBuildData->ShadowMap ? MeshMapBuildData->ShadowMap->GetShadowMap2D() : nullptr;
|
|
if (Shadowmap && Shadowmap->IsValid())
|
|
{
|
|
ReferencedTextures.Add(Shadowmap->GetTexture());
|
|
}
|
|
|
|
FTextureCompilingManager::Get().FinishCompilation(TArray<UTexture*>(MoveTemp(ReferencedTextures)));
|
|
}
|
|
}
|
|
|
|
if((MeshComponent->HLODBatchingPolicy != EHLODBatchingPolicy::None) && InSettings.bIncludeImposters)
|
|
{
|
|
ImposterComponents.Add(MeshComponent);
|
|
}
|
|
else
|
|
{
|
|
StaticMeshComponentsToMerge.Add(MeshComponent);
|
|
}
|
|
|
|
// Save the pivot and asset package name of the first mesh, will later be used for creating merged mesh asset
|
|
if (bFirstMesh)
|
|
{
|
|
// Mesh component pivot point
|
|
MergedAssetPivot = InSettings.bPivotPointAtZero ? FVector::ZeroVector : MeshComponent->GetComponentTransform().GetLocation();
|
|
|
|
// Source mesh asset package name
|
|
MergedAssetPackageName = MeshComponent->GetStaticMesh()->GetOutermost()->GetName();
|
|
|
|
bFirstMesh = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing to do if no StaticMeshComponents
|
|
if (StaticMeshComponentsToMerge.Num() == 0 && ImposterComponents.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FMeshMergeDataTracker DataTracker;
|
|
|
|
const bool bMergeAllLODs = InSettings.LODSelectionType == EMeshLODSelectionType::AllLODs;
|
|
const bool bMergeMaterialData = InSettings.bMergeMaterials && InSettings.LODSelectionType != EMeshLODSelectionType::AllLODs;
|
|
const bool bPropagateMeshData = InSettings.bBakeVertexDataToMesh || (bMergeMaterialData && InSettings.bUseVertexDataForBakingMaterial);
|
|
|
|
TArray<FStaticMeshComponentAdapter> Adapters;
|
|
|
|
TArray<FSectionInfo> Sections;
|
|
if (bMergeAllLODs)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(RetrieveRawMeshData);
|
|
for (int32 ComponentIndex = 0; ComponentIndex < StaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
UStaticMeshComponent* Component = StaticMeshComponentsToMerge[ComponentIndex];
|
|
Adapters.Add(FStaticMeshComponentAdapter(Component));
|
|
FStaticMeshComponentAdapter& Adapter = Adapters.Last();
|
|
|
|
if (InSettings.bComputedLightMapResolution)
|
|
{
|
|
int32 LightMapHeight, LightMapWidth;
|
|
if (Component->GetLightMapResolution(LightMapWidth, LightMapHeight))
|
|
{
|
|
DataTracker.AddLightMapPixels(LightMapWidth * LightMapHeight);
|
|
}
|
|
}
|
|
|
|
const int32 NumLODs = [&]()
|
|
{
|
|
const int32 NumberOfLODsAvailable = Adapter.GetNumberOfLODs();
|
|
if (Component->HLODBatchingPolicy != EHLODBatchingPolicy::None)
|
|
{
|
|
return InSettings.bIncludeImposters ? NumberOfLODsAvailable : NumberOfLODsAvailable - 1;
|
|
}
|
|
|
|
return NumberOfLODsAvailable;
|
|
}();
|
|
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
if (!RetrieveRawMeshData(DataTracker
|
|
, ComponentIndex
|
|
, LODIndex
|
|
, Component
|
|
, bPropagateMeshData
|
|
, Sections
|
|
, Adapter
|
|
, false
|
|
, InSettings))
|
|
{
|
|
//If the rawmesh was not retrieve properly break the loop
|
|
break;
|
|
}
|
|
DataTracker.AddLODIndex(LODIndex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(RetrieveRawMeshData);
|
|
|
|
// Retrieve HLOD module for calculating LOD index from screen size
|
|
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
|
|
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
|
|
|
|
// Adding LOD 0 for merged mesh output
|
|
DataTracker.AddLODIndex(0);
|
|
|
|
// Retrieve mesh and section data for each component
|
|
for (int32 ComponentIndex = 0; ComponentIndex < StaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
// Create material merge adapter for this component
|
|
UStaticMeshComponent* Component = StaticMeshComponentsToMerge[ComponentIndex];
|
|
Adapters.Add(FStaticMeshComponentAdapter(Component));
|
|
FStaticMeshComponentAdapter& Adapter = Adapters.Last();
|
|
|
|
// Determine LOD to use for merging, either user specified or calculated index and ensure we clamp to the maximum LOD index for this adapter
|
|
const int32 LODIndex = [&]()
|
|
{
|
|
int32 LowestDetailLOD = Adapter.GetNumberOfLODs() - 1;
|
|
if (Component->HLODBatchingPolicy != EHLODBatchingPolicy::None && !InSettings.bIncludeImposters)
|
|
{
|
|
LowestDetailLOD = FMath::Max(0, LowestDetailLOD - 1);
|
|
}
|
|
|
|
switch (InSettings.LODSelectionType)
|
|
{
|
|
case EMeshLODSelectionType::SpecificLOD:
|
|
return FMath::Min(LowestDetailLOD, InSettings.SpecificLOD);
|
|
|
|
case EMeshLODSelectionType::CalculateLOD:
|
|
return FMath::Min(LowestDetailLOD, Utilities->GetLODLevelForScreenSize(Component, FMath::Clamp(ScreenSize, 0.0f, 1.0f)));
|
|
|
|
case EMeshLODSelectionType::LowestDetailLOD:
|
|
default:
|
|
return LowestDetailLOD;
|
|
}
|
|
}();
|
|
|
|
RetrieveRawMeshData(DataTracker
|
|
, ComponentIndex
|
|
, LODIndex
|
|
, Component
|
|
, bPropagateMeshData
|
|
, Sections
|
|
, Adapter
|
|
, bMergeMaterialData
|
|
, InSettings);
|
|
}
|
|
}
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(ProcessRawMeshes);
|
|
DataTracker.ProcessRawMeshes();
|
|
}
|
|
|
|
// Merge sockets
|
|
TMap<FName, UStaticMeshSocket*> MergedSockets;
|
|
if (InSettings.bMergeMeshSockets)
|
|
{
|
|
const FTransform PivotTransform = FTransform(MergedAssetPivot);
|
|
for (UPrimitiveComponent* PrimitiveComponent : ComponentsToMerge)
|
|
{
|
|
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(PrimitiveComponent))
|
|
{
|
|
if (UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh())
|
|
{
|
|
for (UStaticMeshSocket* Socket : StaticMesh->Sockets)
|
|
{
|
|
if (Socket)
|
|
{
|
|
UStaticMeshSocket* SocketCopy = DuplicateObject<UStaticMeshSocket>(Socket, nullptr);
|
|
|
|
// Fix name - rename if duplicates are found
|
|
FString PlainName = SocketCopy->SocketName.GetPlainNameString();
|
|
int32 CurrentNumber = SocketCopy->SocketName.GetNumber();
|
|
while (MergedSockets.Contains(SocketCopy->SocketName))
|
|
{
|
|
SocketCopy->SocketName = FName(PlainName, CurrentNumber++);
|
|
}
|
|
|
|
// Fix transform - make relative to pivot
|
|
FTransform SocketTransformWorldSpace = StaticMeshComponent->GetSocketTransform(Socket->SocketName, RTS_World);
|
|
FTransform SocketTransformPivotSpace = SocketTransformWorldSpace.GetRelativeTransform(PivotTransform);
|
|
SocketCopy->RelativeLocation = SocketTransformPivotSpace.GetLocation();
|
|
SocketCopy->RelativeRotation = FRotator(SocketTransformPivotSpace.GetRotation());
|
|
SocketCopy->RelativeScale = SocketTransformPivotSpace.GetScale3D();
|
|
|
|
MergedSockets.Add(SocketCopy->SocketName, SocketCopy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find all unique materials and remap section to unique materials
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
TMap<UMaterialInterface*, UMaterialInterface*> CollapsedMaterialMap;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < DataTracker.NumberOfUniqueSections(); ++SectionIndex)
|
|
{
|
|
// Unique index for material
|
|
UMaterialInterface* MaterialInterface = DataTracker.GetMaterialForSectionIndex(SectionIndex);
|
|
int32 UniqueIndex = UniqueMaterials.IndexOfByPredicate([&InSettings, MaterialInterface](const UMaterialInterface* InMaterialInterface)
|
|
{
|
|
// Perform an optional custom comparison if we are trying to collapse material instances
|
|
if (InSettings.bMergeEquivalentMaterials)
|
|
{
|
|
return FMaterialKey(MaterialInterface) == FMaterialKey(InMaterialInterface);
|
|
}
|
|
return MaterialInterface == InMaterialInterface;
|
|
});
|
|
|
|
if (UniqueIndex == INDEX_NONE)
|
|
{
|
|
UniqueIndex = UniqueMaterials.Add(MaterialInterface);
|
|
}
|
|
|
|
// Update map to 'collapsed' materials
|
|
CollapsedMaterialMap.Add(MaterialInterface, UniqueMaterials[UniqueIndex]);
|
|
}
|
|
|
|
// Retrieve physics data
|
|
UBodySetup* BodySetupSource = nullptr;
|
|
TArray<FKAggregateGeom> PhysicsGeometry;
|
|
if (InSettings.bMergePhysicsData)
|
|
{
|
|
RetrievePhysicsData(ComponentsToMerge, PhysicsGeometry, BodySetupSource);
|
|
}
|
|
|
|
TMultiMap< FMeshLODKey, MaterialRemapPair > OutputMaterialsMap;
|
|
|
|
// If the user wants to merge materials into a single one
|
|
if (bMergeMaterialData && UniqueMaterials.Num() != 0)
|
|
{
|
|
// Create the merged material
|
|
FFlattenMaterial FlattenMaterial;
|
|
CreateMergedMaterial(DataTracker, InSettings, StaticMeshComponentsToMerge, Adapters, UniqueMaterials, CollapsedMaterialMap, OutputMaterialsMap, bMergeAllLODs, bMergeMaterialData, MergedAssetPivot, FlattenMaterial);
|
|
if (FlattenMaterial.HasData())
|
|
{
|
|
// Don't recreate render states with the material update context as we will manually do it through
|
|
// the FStaticMeshComponentRecreateRenderStateContext used below at the creation of the static mesh.
|
|
FMaterialUpdateContext MaterialUpdateContext(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
|
|
|
|
UMaterialInterface* MergedMaterial = CreateProxyMaterial(InBasePackageName, MergedAssetPackageName, InBaseMaterial, InOuter, InSettings, FlattenMaterial, OutAssetsToSync, &MaterialUpdateContext);
|
|
|
|
UniqueMaterials.Empty(1);
|
|
UniqueMaterials.Add(MergedMaterial);
|
|
|
|
FSectionInfo NewSection;
|
|
NewSection.Material = MergedMaterial;
|
|
NewSection.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FStaticMeshSection, bCastShadow));
|
|
DataTracker.AddBakedMaterialSection(NewSection);
|
|
|
|
for (IMeshMergeExtension* Extension : MeshMergeExtensions)
|
|
{
|
|
Extension->OnCreatedProxyMaterial(StaticMeshComponentsToMerge, MergedMaterial);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the merged mesh
|
|
TArray<FMeshDescription> MergedRawMeshes;
|
|
CreateMergedRawMeshes(DataTracker, InSettings, StaticMeshComponentsToMerge, UniqueMaterials, CollapsedMaterialMap, OutputMaterialsMap, bMergeAllLODs, bMergeMaterialData, MergedAssetPivot, MergedRawMeshes);
|
|
|
|
// Notify listeners that our merged mesh was created
|
|
for (IMeshMergeExtension* Extension : MeshMergeExtensions)
|
|
{
|
|
Extension->OnCreatedMergedRawMeshes(StaticMeshComponentsToMerge, DataTracker, MergedRawMeshes);
|
|
}
|
|
|
|
// Populate mesh section map
|
|
FMeshSectionInfoMap SectionInfoMap;
|
|
for (TConstLODIndexIterator Iterator = DataTracker.GetLODIndexIterator(); Iterator; ++Iterator)
|
|
{
|
|
const int32 LODIndex = *Iterator;
|
|
TArray<uint32> UniqueMaterialIndices;
|
|
const FMeshDescription& TargetRawMesh = MergedRawMeshes[LODIndex];
|
|
uint32 MaterialIndex = 0;
|
|
for (FPolygonGroupID PolygonGroupID : TargetRawMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
//Skip empty group
|
|
if (TargetRawMesh.GetPolygonGroupPolygonIDs(PolygonGroupID).Num() > 0)
|
|
{
|
|
if (PolygonGroupID.GetValue() < DataTracker.NumberOfUniqueSections())
|
|
{
|
|
UniqueMaterialIndices.AddUnique(PolygonGroupID.GetValue());
|
|
}
|
|
else
|
|
{
|
|
UniqueMaterialIndices.AddUnique(MaterialIndex);
|
|
}
|
|
MaterialIndex++;
|
|
}
|
|
}
|
|
UniqueMaterialIndices.Sort();
|
|
for (int32 Index = 0; Index < UniqueMaterialIndices.Num(); ++Index)
|
|
{
|
|
const int32 SectionIndex = UniqueMaterialIndices[Index];
|
|
// unclear when this would not be the case, but it seems to be able to occur
|
|
if (SectionIndex < DataTracker.NumberOfUniqueSections())
|
|
{
|
|
const FSectionInfo& StoredSectionInfo = DataTracker.GetSection(SectionIndex);
|
|
FMeshSectionInfo SectionInfo;
|
|
SectionInfo.bCastShadow = StoredSectionInfo.EnabledProperties.Contains(GET_MEMBER_NAME_CHECKED(FMeshSectionInfo, bCastShadow));
|
|
SectionInfo.bEnableCollision = StoredSectionInfo.EnabledProperties.Contains(GET_MEMBER_NAME_CHECKED(FMeshSectionInfo, bEnableCollision));
|
|
SectionInfo.MaterialIndex = UniqueMaterials.Num() == 1 ? 0 : UniqueMaterials.IndexOfByKey(CollapsedMaterialMap[StoredSectionInfo.Material]);
|
|
SectionInfoMap.Set(LODIndex, Index, SectionInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Transform physics primitives to merged mesh pivot
|
|
if (InSettings.bMergePhysicsData && !MergedAssetPivot.IsZero())
|
|
{
|
|
FTransform PivotTM(-MergedAssetPivot);
|
|
for (FKAggregateGeom& Geometry : PhysicsGeometry)
|
|
{
|
|
FMeshMergeHelpers::TransformPhysicsGeometry(PivotTM, false, Geometry);
|
|
}
|
|
}
|
|
|
|
// Compute target lightmap channel for each LOD, by looking at the first empty UV channel
|
|
const int32 LightMapUVChannel = [&]()
|
|
{
|
|
if (InSettings.bGenerateLightMapUV)
|
|
{
|
|
const int32 TempChannel = DataTracker.GetAvailableLightMapUVChannel();
|
|
if (TempChannel == INDEX_NONE)
|
|
{
|
|
// Output warning message
|
|
UE_LOG(LogMeshMerging, Warning, TEXT("Failed to find an available channel for Lightmap UVs. Lightmap UVs will not be generated."));
|
|
}
|
|
return TempChannel;
|
|
}
|
|
|
|
return (int32)INDEX_NONE;
|
|
}();
|
|
|
|
//
|
|
//Create merged mesh asset
|
|
//
|
|
|
|
MergedRawMeshes.SetNum(Algo::RemoveIf(MergedRawMeshes, [](const FMeshDescription& MeshDescription) { return MeshDescription.IsEmpty(); }));
|
|
|
|
const bool bContainsImposters = !ImposterComponents.IsEmpty();
|
|
const bool bContainsMergedMeshes = !MergedRawMeshes.IsEmpty();
|
|
|
|
if (bContainsMergedMeshes || bContainsImposters)
|
|
{
|
|
FString AssetName;
|
|
FString PackageName;
|
|
if (InBasePackageName.IsEmpty())
|
|
{
|
|
AssetName = TEXT("SM_MERGED_") + FPackageName::GetShortName(MergedAssetPackageName);
|
|
PackageName = FPackageName::GetLongPackagePath(MergedAssetPackageName) / AssetName;
|
|
}
|
|
else
|
|
{
|
|
AssetName = TEXT("SM_") + FPackageName::GetShortName(InBasePackageName);
|
|
PackageName = FPackageName::GetLongPackagePath(InBasePackageName) / AssetName;
|
|
}
|
|
|
|
UPackage* Package = InOuter;
|
|
if (Package == nullptr)
|
|
{
|
|
Package = CreatePackage( *PackageName);
|
|
check(Package);
|
|
Package->FullyLoad();
|
|
Package->Modify();
|
|
}
|
|
|
|
// Check that an asset of a different class does not already exist
|
|
{
|
|
UObject* ExistingObject = StaticFindObject( nullptr, Package, *AssetName);
|
|
if(ExistingObject && !ExistingObject->GetClass()->IsChildOf(UStaticMesh::StaticClass()))
|
|
{
|
|
// Change name of merged static mesh to avoid name collision
|
|
UPackage* ParentPackage = CreatePackage( *FPaths::GetPath(Package->GetPathName()));
|
|
ParentPackage->FullyLoad();
|
|
|
|
AssetName = MakeUniqueObjectName( ParentPackage, UStaticMesh::StaticClass(), *AssetName).ToString();
|
|
Package = CreatePackage( *(ParentPackage->GetPathName() / AssetName ));
|
|
check(Package);
|
|
Package->FullyLoad();
|
|
Package->Modify();
|
|
|
|
// Let user know name of merged static mesh has changed
|
|
UE_LOG(LogMeshMerging, Warning,
|
|
TEXT("Cannot create %s %s.%s\n")
|
|
TEXT("An object with the same fully qualified name but a different class already exists.\n")
|
|
TEXT("\tExisting Object: %s\n")
|
|
TEXT("The merged mesh will be named %s.%s"),
|
|
*UStaticMesh::StaticClass()->GetName(), *ExistingObject->GetOutermost()->GetPathName(), *ExistingObject->GetName(),
|
|
*ExistingObject->GetFullName(), *Package->GetPathName(), *AssetName);
|
|
}
|
|
}
|
|
|
|
FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject<UStaticMesh>(Package, *AssetName));
|
|
|
|
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, *AssetName, RF_Public | RF_Standalone);
|
|
StaticMesh->InitResources();
|
|
|
|
FString OutputPath = StaticMesh->GetPathName();
|
|
|
|
// make sure it has a new lighting guid
|
|
StaticMesh->SetLightingGuid();
|
|
if (LightMapUVChannel != INDEX_NONE)
|
|
{
|
|
StaticMesh->SetLightMapResolution(InSettings.TargetLightMapResolution);
|
|
StaticMesh->SetLightMapCoordinateIndex(LightMapUVChannel);
|
|
}
|
|
|
|
// Ray tracing support
|
|
StaticMesh->bSupportRayTracing = InSettings.bSupportRayTracing;
|
|
|
|
TArray<UMaterialInterface*> ImposterMaterials;
|
|
FBox ImposterBounds(EForceInit::ForceInit);
|
|
for (int32 LODIndex = 0; LODIndex < MergedRawMeshes.Num(); ++LODIndex)
|
|
{
|
|
FMeshDescription& MergedMeshLOD = MergedRawMeshes[LODIndex];
|
|
if (MergedMeshLOD.Vertices().Num() > 0 || bContainsImposters)
|
|
{
|
|
FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
|
|
|
|
// Don't allow the engine to recalculate normals
|
|
SrcModel.BuildSettings.bRecomputeNormals = false;
|
|
SrcModel.BuildSettings.bRecomputeTangents = false;
|
|
SrcModel.BuildSettings.bRemoveDegenerates = false;
|
|
SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
|
|
SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
|
|
SrcModel.BuildSettings.bBuildReversedIndexBuffer = false;
|
|
SrcModel.BuildSettings.bGenerateLightmapUVs = LightMapUVChannel != INDEX_NONE;
|
|
SrcModel.BuildSettings.MinLightmapResolution = InSettings.bComputedLightMapResolution ? DataTracker.GetLightMapDimension() : InSettings.TargetLightMapResolution;
|
|
SrcModel.BuildSettings.SrcLightmapIndex = 0;
|
|
SrcModel.BuildSettings.DstLightmapIndex = LightMapUVChannel != INDEX_NONE ? LightMapUVChannel : 0;
|
|
if(!InSettings.bAllowDistanceField)
|
|
{
|
|
SrcModel.BuildSettings.DistanceFieldResolutionScale = 0.0f;
|
|
}
|
|
|
|
if (bContainsImposters)
|
|
{
|
|
// Merge imposter meshes to rawmesh
|
|
FMeshMergeHelpers::MergeImpostersToMesh(ImposterComponents, MergedMeshLOD, MergedAssetPivot, UniqueMaterials.Num(), ImposterMaterials);
|
|
|
|
const FTransform PivotTransform = FTransform(MergedAssetPivot);
|
|
for (const UStaticMeshComponent* Component : ImposterComponents)
|
|
{
|
|
if (Component->GetStaticMesh())
|
|
{
|
|
ImposterBounds += Component->GetStaticMesh()->GetBoundingBox().TransformBy(Component->GetComponentToWorld().GetRelativeTransform(PivotTransform));
|
|
}
|
|
}
|
|
}
|
|
|
|
FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(LODIndex, MergedMeshLOD);
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams CommitParams;
|
|
CommitParams.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(LODIndex, CommitParams);
|
|
}
|
|
}
|
|
|
|
auto IsMaterialImportedNameUnique = [&StaticMesh](FName ImportedMaterialSlotName)
|
|
{
|
|
for (const FStaticMaterial& StaticMaterial : StaticMesh->GetStaticMaterials())
|
|
{
|
|
#if WITH_EDITOR
|
|
if (StaticMaterial.ImportedMaterialSlotName == ImportedMaterialSlotName)
|
|
#else
|
|
if (StaticMaterial.MaterialSlotName == ImportedMaterialSlotName)
|
|
#endif
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
|
|
for (UMaterialInterface* Material : UniqueMaterials)
|
|
{
|
|
if (Material && (!Material->IsAsset() && InOuter != GetTransientPackage()))
|
|
{
|
|
// MIDs are not assets, duplicate them and outer them to the static mesh.
|
|
if (UMaterialInstanceDynamic* MID = Cast<UMaterialInstanceDynamic>(Material))
|
|
{
|
|
Material = DuplicateObject<UMaterialInstanceDynamic>(MID, StaticMesh);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMeshMerging, Error, TEXT("Merged mesh is referencing a non-asset material (\"%s\"). This material can't be referenced outside it's package as it is not public. Clearing out material entry in the final mesh."), *Material->GetPathName());
|
|
Material = nullptr; // do not save non-asset materials
|
|
}
|
|
}
|
|
|
|
//Make sure we have unique slot name here
|
|
FName MaterialSlotName = DataTracker.GetMaterialSlotName(Material);
|
|
int32 Counter = 1;
|
|
while (!IsMaterialImportedNameUnique(MaterialSlotName))
|
|
{
|
|
MaterialSlotName = *(DataTracker.GetMaterialSlotName(Material).ToString() + TEXT("_") + FString::FromInt(Counter++));
|
|
}
|
|
|
|
StaticMesh->GetStaticMaterials().Add(FStaticMaterial(Material, MaterialSlotName));
|
|
}
|
|
|
|
for(UMaterialInterface* ImposterMaterial : ImposterMaterials)
|
|
{
|
|
//Make sure we have unique slot name here
|
|
FName MaterialSlotName = ImposterMaterial->GetFName();
|
|
int32 Counter = 1;
|
|
while (!IsMaterialImportedNameUnique(MaterialSlotName))
|
|
{
|
|
MaterialSlotName = *(ImposterMaterial->GetName() + TEXT("_") + FString::FromInt(Counter++));
|
|
}
|
|
StaticMesh->GetStaticMaterials().Add(FStaticMaterial(ImposterMaterial, MaterialSlotName));
|
|
}
|
|
|
|
if (InSettings.bMergePhysicsData)
|
|
{
|
|
StaticMesh->CreateBodySetup();
|
|
if (BodySetupSource)
|
|
{
|
|
StaticMesh->GetBodySetup()->CopyBodyPropertiesFrom(BodySetupSource);
|
|
}
|
|
|
|
StaticMesh->GetBodySetup()->AggGeom = FKAggregateGeom();
|
|
// Copy collision from the source meshes
|
|
for (const FKAggregateGeom& Geom : PhysicsGeometry)
|
|
{
|
|
StaticMesh->GetBodySetup()->AddCollisionFrom(Geom);
|
|
}
|
|
|
|
// Bake rotation into verts of convex hulls, so they scale correctly after rotation
|
|
for (FKConvexElem& ConvexElem : StaticMesh->GetBodySetup()->AggGeom.ConvexElems)
|
|
{
|
|
ConvexElem.BakeTransformToVerts();
|
|
}
|
|
}
|
|
|
|
// Add merged sockets
|
|
if (InSettings.bMergeMeshSockets)
|
|
{
|
|
for (auto& [SocketName, Socket] : MergedSockets)
|
|
{
|
|
Socket->Rename(nullptr, StaticMesh);
|
|
StaticMesh->AddSocket(Socket);
|
|
}
|
|
}
|
|
|
|
StaticMesh->GetSectionInfoMap().CopyFrom(SectionInfoMap);
|
|
StaticMesh->GetOriginalSectionInfoMap().CopyFrom(SectionInfoMap);
|
|
|
|
//Set the Imported version before calling the build
|
|
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
|
|
StaticMesh->SetLightMapResolution(InSettings.bComputedLightMapResolution ? DataTracker.GetLightMapDimension() : InSettings.TargetLightMapResolution);
|
|
|
|
// Nanite settings
|
|
StaticMesh->NaniteSettings = InSettings.NaniteSettings;
|
|
|
|
#if WITH_EDITOR
|
|
//If we are running the automation test
|
|
if (GIsAutomationTesting)
|
|
{
|
|
StaticMesh->BuildCacheAutomationTestGuid = FGuid::NewGuid();
|
|
}
|
|
#endif
|
|
|
|
// Ensure the new mesh is not referencing non standalone materials
|
|
FMeshMergeHelpers::FixupNonStandaloneMaterialReferences(StaticMesh);
|
|
|
|
|
|
if (ImposterBounds.IsValid)
|
|
{
|
|
const FBox StaticMeshBox = StaticMesh->GetBoundingBox();
|
|
const FBox CombinedBox = StaticMeshBox + ImposterBounds;
|
|
StaticMesh->SetPositiveBoundsExtension((CombinedBox.Max - StaticMeshBox.Max));
|
|
StaticMesh->SetNegativeBoundsExtension((StaticMeshBox.Min - CombinedBox.Min));
|
|
StaticMesh->CalculateExtendedBounds();
|
|
}
|
|
|
|
UStaticMesh::FBuildParameters BuildParameters;
|
|
BuildParameters.bInSilent = true;
|
|
BuildParameters.bInRebuildUVChannelData = true;
|
|
BuildParameters.bInEnforceLightmapRestrictions = true;
|
|
|
|
// Nanite meshes can sometime contain smaller triangles that could be discarded in the proxy mesh.
|
|
// If those are found on the outskirt of a merged mesh, the static mesh build will complain about
|
|
// the bounds delta being too high.
|
|
BuildParameters.bIgnoreBoundsDiff = StaticMesh->NaniteSettings.bEnabled;
|
|
|
|
StaticMesh->Build(BuildParameters);
|
|
|
|
OutAssetsToSync.Add(StaticMesh);
|
|
OutMergedActorLocation = MergedAssetPivot;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMeshMerging, Display, TEXT("MergeComponentsToStaticMesh: Skipped creation of a static mesh asset as no input meshes were provided"));
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateMergedMaterial(FMeshMergeDataTracker& InDataTracker, const FMeshMergingSettings& InSettings, const TArray<UStaticMeshComponent*>& InStaticMeshComponentsToMerge, TArray<FStaticMeshComponentAdapter>& InAdapters, const TArray<UMaterialInterface*>& InUniqueMaterials, const TMap<UMaterialInterface*, UMaterialInterface*>& InCollapsedMaterialMap, TMultiMap<FMeshLODKey, MaterialRemapPair>& InOutputMaterialsMap, bool bInMergeAllLODs, bool bInMergeMaterialData, const FVector& InMergedAssetPivot, FFlattenMaterial& OutFlattenMaterial) const
|
|
{
|
|
OutFlattenMaterial.ReleaseData();
|
|
|
|
TArray<UPrimitiveComponent*> PrimitiveComponents;
|
|
Algo::Transform(InStaticMeshComponentsToMerge, PrimitiveComponents, [](UStaticMeshComponent* SMComponent) { return SMComponent; });
|
|
|
|
FMaterialProxySettings MaterialProxySettings = InSettings.MaterialSettings;
|
|
if (MaterialProxySettings.ResolveTexelDensity(PrimitiveComponents))
|
|
{
|
|
MaterialProxySettings.TextureSize = InDataTracker.GetTextureSizeFromTargetTexelDensity(MaterialProxySettings.TargetTexelDensityPerMeter);
|
|
MaterialProxySettings.TextureSizingType = ETextureSizingType::TextureSizingType_UseSingleTextureSize;
|
|
}
|
|
|
|
UMaterialOptions* MaterialOptions = PopulateMaterialOptions(MaterialProxySettings);
|
|
TGCObjectScopeGuard<UMaterialOptions> MaterialOptionsGCScopeGuard(MaterialOptions);
|
|
|
|
// Check each material to see if the shader actually uses vertex data and collect flags
|
|
TArray<TOptional<bool>> bMaterialUsesVertexData;
|
|
bMaterialUsesVertexData.SetNum(InUniqueMaterials.Num());
|
|
|
|
// Deferred call, as this may not be required by all code paths and is pretty costly to compute
|
|
auto DoesMaterialUsesVertexData = [&](const int32 InMaterialIndex)
|
|
{
|
|
if (!bMaterialUsesVertexData[InMaterialIndex].IsSet())
|
|
{
|
|
bMaterialUsesVertexData[InMaterialIndex] = DetermineMaterialVertexDataUsage(InUniqueMaterials[InMaterialIndex], MaterialOptions);
|
|
}
|
|
|
|
return bMaterialUsesVertexData[InMaterialIndex].GetValue();
|
|
};
|
|
|
|
// For each unique material calculate how 'important' they are
|
|
TArray<float> MaterialImportanceValues;
|
|
FMaterialUtilities::DetermineMaterialImportance(InUniqueMaterials, MaterialImportanceValues);
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
TArray<float> SectionMaterialImportanceValues;
|
|
|
|
TMap<EMaterialProperty, FIntPoint> PropertySizes;
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (!Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
PropertySizes.Add(Entry.Property, Entry.bUseCustomSize ? Entry.CustomSize : MaterialOptions->TextureSize);
|
|
}
|
|
}
|
|
|
|
// If we are generating a single LOD and want to merge materials we can utilize texture space better by generating unique UVs
|
|
// for the merged mesh and baking out materials using those UVs
|
|
const bool bGloballyRemapUVs = !bInMergeAllLODs && !InSettings.bReuseMeshLightmapUVs;
|
|
|
|
typedef TTuple<UStaticMesh*, int32> FMeshLODTuple;
|
|
typedef TFuture<TArray<FVector2D>> FUVComputeFuture;
|
|
TMap<FMeshLODTuple, FUVComputeFuture> MeshLODsTextureCoordinates;
|
|
TMap<int32, FMeshLODTuple> MeshDataAwaitingResults;
|
|
|
|
for (TConstRawMeshIterator RawMeshIterator = InDataTracker.GetConstRawMeshIterator(); RawMeshIterator; ++RawMeshIterator)
|
|
{
|
|
const FMeshLODKey& Key = RawMeshIterator.Key();
|
|
const FMeshDescription& RawMesh = RawMeshIterator.Value();
|
|
const bool bRequiresUniqueUVs = InDataTracker.DoesMeshLODRequireUniqueUVs(Key);
|
|
|
|
const FMeshDescription* MeshDescription = InDataTracker.GetRawMeshPtr(Key);
|
|
UStaticMeshComponent* Component = InStaticMeshComponentsToMerge[Key.GetMeshIndex()];
|
|
UStaticMesh* StaticMesh = Component->GetStaticMesh();
|
|
|
|
// Retrieve all sections and materials for key
|
|
TArray<SectionRemapPair> SectionRemapPairs;
|
|
InDataTracker.GetMappingsForMeshLOD(Key, SectionRemapPairs);
|
|
|
|
// Contains unique materials used for this key, and the accompanying section index which point to the material
|
|
TMap<UMaterialInterface*, TArray<int32>> MaterialAndSectionIndices;
|
|
|
|
for (const SectionRemapPair& RemapPair : SectionRemapPairs)
|
|
{
|
|
const int32 UniqueIndex = RemapPair.Value;
|
|
const int32 SectionIndex = RemapPair.Key;
|
|
TArray<int32>& SectionIndices = MaterialAndSectionIndices.FindOrAdd(InCollapsedMaterialMap.FindChecked(InDataTracker.GetMaterialForSectionIndex(UniqueIndex)));
|
|
SectionIndices.Add(SectionIndex);
|
|
}
|
|
|
|
for (TPair<UMaterialInterface*, TArray<int32>>& MaterialSectionIndexPair : MaterialAndSectionIndices)
|
|
{
|
|
UMaterialInterface* Material = MaterialSectionIndexPair.Key;
|
|
const int32 MaterialIndex = InUniqueMaterials.IndexOfByKey(Material);
|
|
const TArray<int32>& SectionIndices = MaterialSectionIndexPair.Value;
|
|
|
|
FMaterialData MaterialData;
|
|
MaterialData.Material = InCollapsedMaterialMap.FindChecked(Material);
|
|
MaterialData.PropertySizes = PropertySizes;
|
|
|
|
FMeshData NewMeshData;
|
|
const bool bUseMeshData = bGloballyRemapUVs || (InSettings.bUseVertexDataForBakingMaterial && (bRequiresUniqueUVs || DoesMaterialUsesVertexData(MaterialIndex)));
|
|
if (bUseMeshData)
|
|
{
|
|
NewMeshData.Mesh = Key.GetMesh();
|
|
NewMeshData.MeshDescription = MeshDescription;
|
|
NewMeshData.VertexColorHash = Key.GetVertexColorHash();
|
|
NewMeshData.bMirrored = Component->GetComponentTransform().GetDeterminant() < 0.0f;
|
|
NewMeshData.MaterialIndices = SectionIndices;
|
|
if (!Component->GetCustomPrimitiveData().Data.IsEmpty())
|
|
{
|
|
NewMeshData.PrimitiveData = FPrimitiveData();
|
|
NewMeshData.PrimitiveData->CustomPrimitiveData = &Component->GetCustomPrimitiveData();
|
|
}
|
|
}
|
|
|
|
auto CompareMaterialData = [&InSettings](const FMaterialData& LHS, const FMaterialData& RHS)
|
|
{
|
|
return InSettings.bMergeEquivalentMaterials ? FMaterialKey(LHS.Material) == FMaterialKey(RHS.Material) : LHS.Material == RHS.Material;
|
|
};
|
|
|
|
auto CompareCustomPrimitiveData = [](const FCustomPrimitiveData* LHS, const FCustomPrimitiveData* RHS)
|
|
{
|
|
// Return true if both are null, false if one of them is null - otherwise, compare content
|
|
return (!LHS && !RHS) ? true : (!LHS || !RHS) ? false : (*LHS == *RHS);
|
|
};
|
|
|
|
auto ComparePrimitiveData = [&CompareCustomPrimitiveData](const TOptional<FPrimitiveData>& LHS, const TOptional<FPrimitiveData>& RHS)
|
|
{
|
|
// Return true if both are null, false if one of them is null - otherwise, compare content
|
|
return (!LHS && !RHS) ? true : (!LHS || !RHS) ? false : CompareCustomPrimitiveData(LHS->CustomPrimitiveData, RHS->CustomPrimitiveData);
|
|
};
|
|
|
|
auto CompareMeshData = [&ComparePrimitiveData](const FMeshData& LHS, const FMeshData& RHS)
|
|
{
|
|
return (LHS.Mesh == RHS.Mesh) && (LHS.MaterialIndices == RHS.MaterialIndices) && (LHS.bMirrored == RHS.bMirrored) && (LHS.VertexColorHash == RHS.VertexColorHash) && ComparePrimitiveData(LHS.PrimitiveData, RHS.PrimitiveData);
|
|
};
|
|
|
|
// Find material & mesh pair
|
|
int32 MeshDataIndex = INDEX_NONE;
|
|
for (int32 GlobalMaterialSettingsIndex = 0; GlobalMaterialSettingsIndex < GlobalMaterialSettings.Num(); ++GlobalMaterialSettingsIndex)
|
|
{
|
|
if (CompareMaterialData(GlobalMaterialSettings[GlobalMaterialSettingsIndex], MaterialData) &&
|
|
CompareMeshData(GlobalMeshSettings[GlobalMaterialSettingsIndex], NewMeshData))
|
|
{
|
|
MeshDataIndex = GlobalMaterialSettingsIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We've found a match, no need to process this mesh/material pair
|
|
if (MeshDataIndex == INDEX_NONE)
|
|
{
|
|
// We're processing a new pair
|
|
MeshDataIndex = GlobalMeshSettings.Num();
|
|
|
|
FMeshData& MeshData = GlobalMeshSettings.Emplace_GetRef(NewMeshData);
|
|
GlobalMaterialSettings.Add(MaterialData);
|
|
SectionMaterialImportanceValues.Add(MaterialImportanceValues[MaterialIndex]);
|
|
|
|
if (bUseMeshData)
|
|
{
|
|
// if it has vertex color/*WedgetColors.Num()*/, it should also use light map UV index
|
|
// we can't do this for all meshes, but only for the mesh that has vertex color.
|
|
if (bRequiresUniqueUVs || MeshData.MeshDescription->VertexInstances().Num() > 0)
|
|
{
|
|
// Check if there are lightmap uvs available?
|
|
const int32 LightMapUVIndex = StaticMesh->GetLightMapCoordinateIndex();
|
|
|
|
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = FStaticMeshConstAttributes(*MeshData.MeshDescription).GetVertexInstanceUVs();
|
|
if (InSettings.bReuseMeshLightmapUVs && VertexInstanceUVs.GetNumElements() > 0 && VertexInstanceUVs.GetNumChannels() > LightMapUVIndex)
|
|
{
|
|
MeshData.TextureCoordinateIndex = LightMapUVIndex;
|
|
}
|
|
else
|
|
{
|
|
// Verify if we started an async task to generate UVs for this static mesh & LOD
|
|
FMeshLODTuple Tuple(Key.GetMesh(), Key.GetLODIndex());
|
|
if (!MeshLODsTextureCoordinates.Find(Tuple))
|
|
{
|
|
// No job found yet, fire an async task
|
|
MeshLODsTextureCoordinates.Add(Tuple, Async(EAsyncExecution::Thread, [MeshDescription, MaterialOptions, this]()
|
|
{
|
|
FStaticMeshOperations::FGenerateUVOptions GenerateUVOptions;
|
|
GenerateUVOptions.TextureResolution = MaterialOptions->TextureSize.GetMax();
|
|
GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes = false;
|
|
GenerateUVOptions.UVMethod = GetUVGenerationMethodToUse();
|
|
|
|
TArray<FVector2D> UniqueTextureCoordinates;
|
|
FStaticMeshOperations::GenerateUV(*MeshDescription, GenerateUVOptions, UniqueTextureCoordinates);
|
|
|
|
if (GenerateUVOptions.UVMethod == FStaticMeshOperations::EGenerateUVMethod::Legacy)
|
|
{
|
|
ScaleTextureCoordinatesToBox(FBox2D(FVector2D::ZeroVector, FVector2D(1, 1)), UniqueTextureCoordinates);
|
|
}
|
|
|
|
return UniqueTextureCoordinates;
|
|
}));
|
|
}
|
|
// Keep track of the fact that this mesh is waiting for the UV computation to finish
|
|
MeshDataAwaitingResults.Add(MeshDataIndex, Tuple);
|
|
}
|
|
}
|
|
|
|
InAdapters[Key.GetMeshIndex()].ApplySettings(Key.GetLODIndex(), MeshData);
|
|
}
|
|
}
|
|
|
|
for (const auto& OriginalSectionIndex : SectionIndices)
|
|
{
|
|
InOutputMaterialsMap.Add(Key, MaterialRemapPair(OriginalSectionIndex, MeshDataIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fetch results from the async UV computation tasks
|
|
for (auto MeshData : MeshDataAwaitingResults)
|
|
{
|
|
GlobalMeshSettings[MeshData.Key].CustomTextureCoordinates = MeshLODsTextureCoordinates[MeshData.Value].Get();
|
|
}
|
|
|
|
TArray<FMeshData*> MeshSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMeshSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MeshSettingPtrs.Add(&GlobalMeshSettings[SettingsIndex]);
|
|
}
|
|
|
|
TArray<FMaterialData*> MaterialSettingPtrs;
|
|
for (int32 SettingsIndex = 0; SettingsIndex < GlobalMaterialSettings.Num(); ++SettingsIndex)
|
|
{
|
|
MaterialSettingPtrs.Add(&GlobalMaterialSettings[SettingsIndex]);
|
|
}
|
|
|
|
if (bGloballyRemapUVs)
|
|
{
|
|
// Adjust merge settings when merging for the unique UV/material baking pass
|
|
// The final merged mesh will use the original settings
|
|
FMeshMergingSettings RemapUVMergeSettings = InSettings;
|
|
|
|
// Keep vertex data in order to properly generate unique UVs
|
|
RemapUVMergeSettings.bBakeVertexDataToMesh = true;
|
|
|
|
// Keep all UVs as some channels might be needed to properly bake the material
|
|
for (EUVOutput& OutputUV : RemapUVMergeSettings.OutputUVs)
|
|
{
|
|
OutputUV = EUVOutput::OutputChannel;
|
|
}
|
|
|
|
TArray<FMeshDescription> MergedRawMeshes;
|
|
CreateMergedRawMeshes(InDataTracker, RemapUVMergeSettings, InStaticMeshComponentsToMerge, InUniqueMaterials, InCollapsedMaterialMap, InOutputMaterialsMap, false, false, InMergedAssetPivot, MergedRawMeshes);
|
|
|
|
// Create texture coords for the merged mesh
|
|
FStaticMeshOperations::FGenerateUVOptions GenerateUVOptions;
|
|
GenerateUVOptions.TextureResolution = MaterialOptions->TextureSize.GetMax();
|
|
GenerateUVOptions.bMergeTrianglesWithIdenticalAttributes = true;
|
|
GenerateUVOptions.UVMethod = GetUVGenerationMethodToUse();
|
|
|
|
TArray<FVector2D> GlobalTextureCoordinates;
|
|
bool bSuccess = FStaticMeshOperations::GenerateUV(MergedRawMeshes[0], GenerateUVOptions, GlobalTextureCoordinates);
|
|
if (bSuccess)
|
|
{
|
|
if (GenerateUVOptions.UVMethod == FStaticMeshOperations::EGenerateUVMethod::Legacy)
|
|
{
|
|
ScaleTextureCoordinatesToBox(FBox2D(FVector2D::ZeroVector, FVector2D(1, 1)), GlobalTextureCoordinates);
|
|
}
|
|
|
|
// copy UVs back to the un-merged mesh's custom texture coords
|
|
// iterate the raw meshes in the same way as when we combined the mesh above in CreateMergedRawMeshes()
|
|
int32 GlobalUVIndex = 0;
|
|
for (TConstRawMeshIterator RawMeshIterator = InDataTracker.GetConstRawMeshIterator(); RawMeshIterator; ++RawMeshIterator)
|
|
{
|
|
const FMeshLODKey& Key = RawMeshIterator.Key();
|
|
const FMeshDescription& RawMesh = RawMeshIterator.Value();
|
|
|
|
// Build a local array for this raw mesh
|
|
TArray<FVector2D> UniqueTextureCoordinates;
|
|
UniqueTextureCoordinates.SetNumUninitialized(RawMesh.VertexInstances().Num());
|
|
for (FVector2D& UniqueTextureCoordinate : UniqueTextureCoordinates)
|
|
{
|
|
UniqueTextureCoordinate = GlobalTextureCoordinates[GlobalUVIndex++];
|
|
}
|
|
|
|
// copy to mesh data
|
|
for (FMeshData& MeshData : GlobalMeshSettings)
|
|
{
|
|
if (MeshData.MeshDescription == &RawMesh)
|
|
{
|
|
MeshData.CustomTextureCoordinates = UniqueTextureCoordinates;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dont smear borders as we will copy back non-pink pixels
|
|
for (FMaterialData& MaterialData : GlobalMaterialSettings)
|
|
{
|
|
MaterialData.bPerformBorderSmear = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMeshMerging, Warning, TEXT("GenerateUV: Failed to pack UVs for static mesh"));
|
|
}
|
|
}
|
|
|
|
TArray<FFlattenMaterial> FlattenedMaterials;
|
|
// This scope ensures BakeOutputs is never used after TransferOutputToFlatMaterials
|
|
{
|
|
TArray<FBakeOutput> BakeOutputs;
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
|
|
// If we're working with a new set of UVs, we can bake all materials directly to the same bake output
|
|
// as our remapped UVs for each mesh don't overlap.
|
|
if (bGloballyRemapUVs)
|
|
{
|
|
FBakeOutput& BakeOutput = BakeOutputs.Emplace_GetRef();
|
|
Module.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutput);
|
|
}
|
|
else
|
|
{
|
|
Module.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs);
|
|
}
|
|
|
|
// Append constant properties ?
|
|
TArray<FColor> ConstantData;
|
|
FIntPoint ConstantSize(1, 1);
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (Entry.bUseConstantValue && Entry.Property != MP_MAX)
|
|
{
|
|
ConstantData.SetNum(1, EAllowShrinking::No);
|
|
ConstantData[0] = FLinearColor(Entry.ConstantValue, Entry.ConstantValue, Entry.ConstantValue).ToFColor(true);
|
|
for (FBakeOutput& Output : BakeOutputs)
|
|
{
|
|
Output.PropertyData.Add(Entry.Property, ConstantData);
|
|
Output.PropertySizes.Add(Entry.Property, ConstantSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TransferOutputToFlatMaterials(GlobalMaterialSettings, BakeOutputs, FlattenedMaterials);
|
|
}
|
|
|
|
if (!bGloballyRemapUVs)
|
|
{
|
|
// Try to optimize materials where possible
|
|
for (FFlattenMaterial& InMaterial : FlattenedMaterials)
|
|
{
|
|
FMaterialUtilities::OptimizeFlattenMaterial(InMaterial);
|
|
}
|
|
}
|
|
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (Entry.Property != MP_MAX)
|
|
{
|
|
EFlattenMaterialProperties OldProperty = ToFlattenProperty(Entry.Property);
|
|
if (ensure(OldProperty != EFlattenMaterialProperties::NumFlattenMaterialProperties))
|
|
{
|
|
OutFlattenMaterial.SetPropertySize(OldProperty, Entry.bUseCustomSize ? Entry.CustomSize : MaterialOptions->TextureSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FUVOffsetScalePair> UVTransforms;
|
|
if (bGloballyRemapUVs)
|
|
{
|
|
// If we have globally remapped UVs we copy non-pink pixels over the dest texture rather than
|
|
// copying sub-charts
|
|
TArray<FBox2D> MaterialBoxes;
|
|
MaterialBoxes.SetNumUninitialized(GlobalMaterialSettings.Num());
|
|
for (FBox2D& Box2D : MaterialBoxes)
|
|
{
|
|
Box2D = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
}
|
|
|
|
FlattenBinnedMaterials(FlattenedMaterials, MaterialBoxes, 0, true, OutFlattenMaterial, UVTransforms);
|
|
|
|
static const FUVOffsetScalePair NoUVTransform = { FVector2D::Zero(), FVector2D::One() };
|
|
UVTransforms.Init(NoUVTransform, GlobalMaterialSettings.Num());
|
|
}
|
|
else
|
|
{
|
|
/** Reweighting */
|
|
float TotalValue = 0.0f;
|
|
for (const float& Value : SectionMaterialImportanceValues)
|
|
{
|
|
TotalValue += Value;
|
|
}
|
|
|
|
float Multiplier = 1.0f / TotalValue;
|
|
|
|
for (float& Value : SectionMaterialImportanceValues)
|
|
{
|
|
Value *= Multiplier;
|
|
}
|
|
/** End reweighting */
|
|
|
|
if (InSettings.bUseTextureBinning)
|
|
{
|
|
TArray<FBox2D> MaterialBoxes;
|
|
FMaterialUtilities::GeneratedBinnedTextureSquares(FVector2D(1.0f, 1.0f), SectionMaterialImportanceValues, MaterialBoxes);
|
|
FlattenBinnedMaterials(FlattenedMaterials, MaterialBoxes, InSettings.GutterSize, false, OutFlattenMaterial, UVTransforms);
|
|
}
|
|
else
|
|
{
|
|
MergeFlattenedMaterials(FlattenedMaterials, InSettings.GutterSize, OutFlattenMaterial, UVTransforms);
|
|
}
|
|
}
|
|
|
|
// Adjust UVs
|
|
for (int32 ComponentIndex = 0; ComponentIndex < InStaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
TArray<uint32> ProcessedMaterials;
|
|
for (TPair<FMeshLODKey, MaterialRemapPair>& MappingPair : InOutputMaterialsMap)
|
|
{
|
|
if (MappingPair.Key.GetMeshIndex() == ComponentIndex && !ProcessedMaterials.Contains(MappingPair.Value.Key))
|
|
{
|
|
// Retrieve raw mesh data for this component and lod pair
|
|
FMeshDescription* RawMesh = InDataTracker.GetRawMeshPtr(MappingPair.Key);
|
|
|
|
FMeshData& MeshData = GlobalMeshSettings[MappingPair.Value.Value];
|
|
const FUVOffsetScalePair& UVTransform = UVTransforms[MappingPair.Value.Value];
|
|
|
|
const uint32 MaterialIndex = MappingPair.Value.Key;
|
|
ProcessedMaterials.Add(MaterialIndex);
|
|
if (RawMesh->Vertices().Num())
|
|
{
|
|
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = FStaticMeshAttributes(*RawMesh).GetVertexInstanceUVs();
|
|
int32 NumUVChannel = FMath::Min(VertexInstanceUVs.GetNumChannels(), (int32)MAX_MESH_TEXTURE_COORDS);
|
|
for (int32 UVChannelIdx = 0; UVChannelIdx < NumUVChannel; ++UVChannelIdx)
|
|
{
|
|
int32 VertexIndex = 0;
|
|
for (FVertexInstanceID VertexInstanceID : RawMesh->VertexInstances().GetElementIDs())
|
|
{
|
|
FVector2D UV = FVector2D(VertexInstanceUVs.Get(VertexInstanceID, UVChannelIdx));
|
|
if (UVChannelIdx == 0)
|
|
{
|
|
if (MeshData.CustomTextureCoordinates.Num())
|
|
{
|
|
UV = MeshData.CustomTextureCoordinates[VertexIndex];
|
|
}
|
|
else if (MeshData.TextureCoordinateIndex != 0)
|
|
{
|
|
check(MeshData.TextureCoordinateIndex < NumUVChannel);
|
|
UV = FVector2D(VertexInstanceUVs.Get(VertexInstanceID, MeshData.TextureCoordinateIndex));
|
|
}
|
|
}
|
|
|
|
const TArray<FPolygonID>& Polygons = RawMesh->GetVertexInstanceConnectedPolygons(VertexInstanceID);
|
|
for (FPolygonID PolygonID : Polygons)
|
|
{
|
|
FPolygonGroupID PolygonGroupID = RawMesh->GetPolygonPolygonGroup(PolygonID);
|
|
if (PolygonGroupID.GetValue() == MaterialIndex)
|
|
{
|
|
if (UVTransform.Value != FVector2D::ZeroVector)
|
|
{
|
|
VertexInstanceUVs.Set(VertexInstanceID, UVChannelIdx, FVector2f(UV * UVTransform.Value + UVTransform.Key)); // LWC_TODO: Precision loss
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
VertexIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TRawMeshIterator Iterator = InDataTracker.GetRawMeshIterator(); Iterator; ++Iterator)
|
|
{
|
|
FMeshDescription& RawMesh = Iterator.Value();
|
|
// Reset material indexes
|
|
TMap<FPolygonGroupID, FPolygonGroupID> RemapPolygonGroups;
|
|
for (FPolygonGroupID PolygonGroupID : RawMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
RemapPolygonGroups.Add(PolygonGroupID, FPolygonGroupID(0));
|
|
}
|
|
RawMesh.RemapPolygonGroups(RemapPolygonGroups);
|
|
}
|
|
|
|
OutFlattenMaterial.UVChannel = INDEX_NONE;
|
|
}
|
|
|
|
void FMeshMergeUtilities::CreateMergedRawMeshes(FMeshMergeDataTracker& InDataTracker, const FMeshMergingSettings& InSettings, const TArray<UStaticMeshComponent*>& InStaticMeshComponentsToMerge, const TArray<UMaterialInterface*>& InUniqueMaterials, const TMap<UMaterialInterface*, UMaterialInterface*>& InCollapsedMaterialMap, const TMultiMap<FMeshLODKey, MaterialRemapPair>& InOutputMaterialsMap, bool bInMergeAllLODs, bool bInMergeMaterialData, const FVector& InMergedAssetPivot, TArray<FMeshDescription>& OutMergedRawMeshes) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshMergeUtilities::CreateMergedRawMeshes)
|
|
|
|
if (bInMergeAllLODs)
|
|
{
|
|
OutMergedRawMeshes.AddDefaulted(InDataTracker.GetNumLODsForMergedMesh());
|
|
for (TConstLODIndexIterator Iterator = InDataTracker.GetLODIndexIterator(); Iterator; ++Iterator)
|
|
{
|
|
// Find meshes for each lod
|
|
const int32 LODIndex = *Iterator;
|
|
FMeshDescription& MergedMesh = OutMergedRawMeshes[LODIndex];
|
|
FStaticMeshAttributes(MergedMesh).Register();
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < InStaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
int32 RetrievedLODIndex = LODIndex;
|
|
FMeshDescription* RawMeshPtr = InDataTracker.TryFindRawMeshForLOD(ComponentIndex, RetrievedLODIndex);
|
|
|
|
if (RawMeshPtr != nullptr)
|
|
{
|
|
InDataTracker.AddComponentToWedgeMapping(ComponentIndex, LODIndex, MergedMesh.VertexInstances().Num());
|
|
|
|
FStaticMeshOperations::FAppendSettings AppendSettings;
|
|
|
|
AppendSettings.PolygonGroupsDelegate = FAppendPolygonGroupsDelegate::CreateLambda([&bInMergeMaterialData, &InDataTracker, &InOutputMaterialsMap, &ComponentIndex, &LODIndex](const FMeshDescription& SourceMesh, FMeshDescription& TargetMesh, PolygonGroupMap& RemapPolygonGroups)
|
|
{
|
|
TPolygonGroupAttributesConstRef<FName> SourceImportedMaterialSlotNames = SourceMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
TPolygonGroupAttributesRef<FName> TargetImportedMaterialSlotNames = TargetMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
//Copy the polygon group
|
|
if (bInMergeMaterialData)
|
|
{
|
|
FPolygonGroupID PolygonGroupID(0);
|
|
if (!TargetMesh.PolygonGroups().IsValid(PolygonGroupID))
|
|
{
|
|
TargetMesh.CreatePolygonGroupWithID(PolygonGroupID);
|
|
TargetImportedMaterialSlotNames[PolygonGroupID] = SourceMesh.PolygonGroups().IsValid(PolygonGroupID) ? SourceImportedMaterialSlotNames[PolygonGroupID] : FName(TEXT("DefaultMaterialName"));
|
|
}
|
|
for (FPolygonGroupID SourcePolygonGroupID : SourceMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
RemapPolygonGroups.Add(SourcePolygonGroupID, PolygonGroupID);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<SectionRemapPair> SectionMappings;
|
|
InDataTracker.GetMappingsForMeshLOD(FMeshLODKey(ComponentIndex, LODIndex), SectionMappings);
|
|
for (FPolygonGroupID SourcePolygonGroupID : SourceMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
// First map from original section index to unique material index
|
|
int32 UniqueIndex = INDEX_NONE;
|
|
// then map to the output material map, if any
|
|
if (InOutputMaterialsMap.Num() > 0)
|
|
{
|
|
TArray<MaterialRemapPair> MaterialMappings;
|
|
InOutputMaterialsMap.MultiFind(FMeshLODKey(ComponentIndex, LODIndex), MaterialMappings);
|
|
for (MaterialRemapPair& Pair : MaterialMappings)
|
|
{
|
|
if (Pair.Key == SourcePolygonGroupID.GetValue())
|
|
{
|
|
UniqueIndex = Pair.Value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Note that at this point UniqueIndex is NOT a material index, but a unique section index!
|
|
}
|
|
|
|
if(UniqueIndex == INDEX_NONE)
|
|
{
|
|
UniqueIndex = SourcePolygonGroupID.GetValue();
|
|
}
|
|
FPolygonGroupID TargetPolygonGroupID(UniqueIndex);
|
|
if (!TargetMesh.PolygonGroups().IsValid(TargetPolygonGroupID))
|
|
{
|
|
while (TargetMesh.PolygonGroups().Num() <= UniqueIndex)
|
|
{
|
|
TargetPolygonGroupID = TargetMesh.CreatePolygonGroup();
|
|
}
|
|
check(TargetPolygonGroupID.GetValue() == UniqueIndex);
|
|
TargetImportedMaterialSlotNames[TargetPolygonGroupID] = SourceImportedMaterialSlotNames[SourcePolygonGroupID];
|
|
}
|
|
RemapPolygonGroups.Add(SourcePolygonGroupID, TargetPolygonGroupID);
|
|
}
|
|
}
|
|
});
|
|
AppendSettings.bMergeVertexColor = InSettings.bBakeVertexDataToMesh;
|
|
AppendSettings.MergedAssetPivot = InMergedAssetPivot;
|
|
for (int32 ChannelIdx = 0; ChannelIdx < FStaticMeshOperations::FAppendSettings::MAX_NUM_UV_CHANNELS; ++ChannelIdx)
|
|
{
|
|
AppendSettings.bMergeUVChannels[ChannelIdx] = InDataTracker.DoesUVChannelContainData(ChannelIdx, LODIndex) && InSettings.OutputUVs[ChannelIdx] == EUVOutput::OutputChannel;
|
|
}
|
|
FStaticMeshOperations::AppendMeshDescription(*RawMeshPtr, MergedMesh, AppendSettings);
|
|
}
|
|
}
|
|
|
|
//Cleanup the empty material to avoid empty section later
|
|
TArray<FPolygonGroupID> PolygonGroupToRemove;
|
|
for (FPolygonGroupID PolygonGroupID : MergedMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
if (MergedMesh.GetPolygonGroupPolygonIDs(PolygonGroupID).Num() < 1)
|
|
{
|
|
PolygonGroupToRemove.Add(PolygonGroupID);
|
|
|
|
}
|
|
}
|
|
for (FPolygonGroupID PolygonGroupID : PolygonGroupToRemove)
|
|
{
|
|
MergedMesh.DeletePolygonGroup(PolygonGroupID);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FMeshDescription& MergedMesh = OutMergedRawMeshes.AddDefaulted_GetRef();
|
|
FStaticMeshAttributes(MergedMesh).Register();
|
|
|
|
for (int32 ComponentIndex = 0; ComponentIndex < InStaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
int32 LODIndex = 0;
|
|
|
|
FMeshDescription* RawMeshPtr = InDataTracker.FindRawMeshAndLODIndex(ComponentIndex, LODIndex);
|
|
|
|
if (RawMeshPtr != nullptr)
|
|
{
|
|
FMeshDescription& RawMesh = *RawMeshPtr;
|
|
|
|
const int32 TargetLODIndex = 0;
|
|
InDataTracker.AddComponentToWedgeMapping(ComponentIndex, TargetLODIndex, MergedMesh.VertexInstances().Num());
|
|
|
|
FStaticMeshOperations::FAppendSettings AppendSettings;
|
|
|
|
AppendSettings.PolygonGroupsDelegate = FAppendPolygonGroupsDelegate::CreateLambda([&bInMergeMaterialData, &InDataTracker, &InOutputMaterialsMap, &ComponentIndex, &LODIndex](const FMeshDescription& SourceMesh, FMeshDescription& TargetMesh, PolygonGroupMap& RemapPolygonGroups)
|
|
{
|
|
TPolygonGroupAttributesConstRef<FName> SourceImportedMaterialSlotNames = SourceMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
TPolygonGroupAttributesRef<FName> TargetImportedMaterialSlotNames = TargetMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
//Copy the polygon group
|
|
if (bInMergeMaterialData)
|
|
{
|
|
FPolygonGroupID PolygonGroupID(0);
|
|
if (!TargetMesh.PolygonGroups().IsValid(PolygonGroupID))
|
|
{
|
|
TargetMesh.CreatePolygonGroupWithID(PolygonGroupID);
|
|
TargetImportedMaterialSlotNames[PolygonGroupID] = SourceMesh.PolygonGroups().IsValid(PolygonGroupID) ? SourceImportedMaterialSlotNames[PolygonGroupID] : FName(TEXT("DefaultMaterialName"));
|
|
}
|
|
for (FPolygonGroupID SourcePolygonGroupID : SourceMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
RemapPolygonGroups.Add(SourcePolygonGroupID, PolygonGroupID);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<SectionRemapPair> SectionMappings;
|
|
InDataTracker.GetMappingsForMeshLOD(FMeshLODKey(ComponentIndex, LODIndex), SectionMappings);
|
|
for (FPolygonGroupID SourcePolygonGroupID : SourceMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
// First map from original section index to unique material index
|
|
int32 UniqueIndex = INDEX_NONE;
|
|
// then map to the output material map, if any
|
|
if (InOutputMaterialsMap.Num() > 0)
|
|
{
|
|
TArray<MaterialRemapPair> MaterialMappings;
|
|
InOutputMaterialsMap.MultiFind(FMeshLODKey(ComponentIndex, LODIndex), MaterialMappings);
|
|
for (MaterialRemapPair& Pair : MaterialMappings)
|
|
{
|
|
if (Pair.Key == SourcePolygonGroupID.GetValue())
|
|
{
|
|
UniqueIndex = Pair.Value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Note that at this point UniqueIndex is NOT a material index, but a unique section index!
|
|
}
|
|
|
|
//Fallback
|
|
if(UniqueIndex == INDEX_NONE)
|
|
{
|
|
UniqueIndex = SourcePolygonGroupID.GetValue();
|
|
}
|
|
|
|
FPolygonGroupID TargetPolygonGroupID(UniqueIndex);
|
|
if (!TargetMesh.PolygonGroups().IsValid(TargetPolygonGroupID))
|
|
{
|
|
while (TargetMesh.PolygonGroups().Num() <= UniqueIndex)
|
|
{
|
|
TargetPolygonGroupID = TargetMesh.CreatePolygonGroup();
|
|
}
|
|
check(TargetPolygonGroupID.GetValue() == UniqueIndex);
|
|
TargetImportedMaterialSlotNames[TargetPolygonGroupID] = SourceImportedMaterialSlotNames[SourcePolygonGroupID];
|
|
}
|
|
RemapPolygonGroups.Add(SourcePolygonGroupID, TargetPolygonGroupID);
|
|
}
|
|
}
|
|
});
|
|
AppendSettings.bMergeVertexColor = InSettings.bBakeVertexDataToMesh;
|
|
AppendSettings.MergedAssetPivot = InMergedAssetPivot;
|
|
for (int32 ChannelIdx = 0; ChannelIdx < FStaticMeshOperations::FAppendSettings::MAX_NUM_UV_CHANNELS; ++ChannelIdx)
|
|
{
|
|
AppendSettings.bMergeUVChannels[ChannelIdx] = InDataTracker.DoesUVChannelContainData(ChannelIdx, LODIndex) && InSettings.OutputUVs[ChannelIdx] == EUVOutput::OutputChannel;
|
|
}
|
|
FStaticMeshOperations::AppendMeshDescription(*RawMeshPtr, MergedMesh, AppendSettings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::MergeComponentsToInstances(const TArray<UPrimitiveComponent*>& ComponentsToMerge, UWorld* World, ULevel* Level, const FMeshInstancingSettings& InSettings, bool bActuallyMerge /*= true*/, bool bReplaceSourceActors /* = false */, FText* OutResultsText /*= nullptr*/) const
|
|
{
|
|
auto HasInstanceVertexColors = [](UStaticMeshComponent* StaticMeshComponent)
|
|
{
|
|
for (const FStaticMeshComponentLODInfo& CurrentLODInfo : StaticMeshComponent->LODData)
|
|
{
|
|
if(CurrentLODInfo.OverrideVertexColors != nullptr || CurrentLODInfo.PaintedVertices.Num() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Gather valid components
|
|
TArray<UStaticMeshComponent*> ValidComponents;
|
|
for(UPrimitiveComponent* ComponentToMerge : ComponentsToMerge)
|
|
{
|
|
if(UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(ComponentToMerge))
|
|
{
|
|
// Dont harvest from 'destination' actors
|
|
if(StaticMeshComponent->GetOwner()->GetClass() != InSettings.ActorClassToUse.Get())
|
|
{
|
|
if( !InSettings.bSkipMeshesWithVertexColors || !HasInstanceVertexColors(StaticMeshComponent))
|
|
{
|
|
ValidComponents.Add(StaticMeshComponent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(OutResultsText != nullptr)
|
|
{
|
|
*OutResultsText = LOCTEXT("InstanceMergePredictedResultsNone", "The current settings will not result in any instanced meshes being created");
|
|
}
|
|
|
|
if(ValidComponents.Num() > 0)
|
|
{
|
|
/** Helper struct representing a spawned ISMC */
|
|
struct FComponentEntry
|
|
{
|
|
FComponentEntry(UStaticMeshComponent* InComponent)
|
|
{
|
|
StaticMesh = InComponent->GetStaticMesh();
|
|
InComponent->GetUsedMaterials(Materials);
|
|
bReverseCulling = InComponent->GetComponentTransform().ToMatrixWithScale().Determinant() < 0.0f;
|
|
CollisionProfileName = InComponent->GetCollisionProfileName();
|
|
CollisionEnabled = InComponent->GetCollisionEnabled();
|
|
OriginalComponents.Add(InComponent);
|
|
}
|
|
|
|
bool operator==(const FComponentEntry& InOther) const
|
|
{
|
|
return
|
|
StaticMesh == InOther.StaticMesh &&
|
|
Materials == InOther.Materials &&
|
|
bReverseCulling == InOther.bReverseCulling &&
|
|
CollisionProfileName == InOther.CollisionProfileName &&
|
|
CollisionEnabled == InOther.CollisionEnabled;
|
|
}
|
|
|
|
UStaticMesh* StaticMesh;
|
|
|
|
TArray<UMaterialInterface*> Materials;
|
|
|
|
TArray<UStaticMeshComponent*> OriginalComponents;
|
|
|
|
FName CollisionProfileName;
|
|
|
|
bool bReverseCulling;
|
|
|
|
ECollisionEnabled::Type CollisionEnabled;
|
|
};
|
|
|
|
/** Helper struct representing a spawned ISMC-containing actor */
|
|
struct FActorEntry
|
|
{
|
|
FActorEntry(UStaticMeshComponent* InComponent, ULevel* InLevel)
|
|
: MergedActor(nullptr)
|
|
{
|
|
// intersect with HLOD volumes if we have a level
|
|
if(InLevel)
|
|
{
|
|
for (AActor* Actor : InLevel->Actors)
|
|
{
|
|
if (AHierarchicalLODVolume* HierarchicalLODVolume = Cast<AHierarchicalLODVolume>(Actor))
|
|
{
|
|
FBox BoundingBox = InComponent->Bounds.GetBox();
|
|
FBox VolumeBox = HierarchicalLODVolume->GetComponentsBoundingBox(true);
|
|
|
|
if (VolumeBox.IsInside(BoundingBox) || (HierarchicalLODVolume->bIncludeOverlappingActors && VolumeBox.Intersect(BoundingBox)))
|
|
{
|
|
HLODVolume = HierarchicalLODVolume;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool operator==(const FActorEntry& InOther) const
|
|
{
|
|
return HLODVolume == InOther.HLODVolume;
|
|
}
|
|
|
|
AActor* MergedActor;
|
|
AHierarchicalLODVolume* HLODVolume;
|
|
TArray<FComponentEntry> ComponentEntries;
|
|
};
|
|
|
|
// Gather a list of components to merge
|
|
TArray<FActorEntry> ActorEntries;
|
|
for(UStaticMeshComponent* StaticMeshComponent : ValidComponents)
|
|
{
|
|
int32 ActorEntryIndex = ActorEntries.AddUnique(FActorEntry(StaticMeshComponent, InSettings.bUseHLODVolumes ? Level : nullptr));
|
|
FActorEntry& ActorEntry = ActorEntries[ActorEntryIndex];
|
|
|
|
FComponentEntry ComponentEntry(StaticMeshComponent);
|
|
|
|
if(FComponentEntry* ExistingComponentEntry = ActorEntry.ComponentEntries.FindByKey(ComponentEntry))
|
|
{
|
|
ExistingComponentEntry->OriginalComponents.Add(StaticMeshComponent);
|
|
}
|
|
else
|
|
{
|
|
ActorEntry.ComponentEntries.Add(ComponentEntry);
|
|
}
|
|
}
|
|
|
|
// Filter by component count
|
|
for(FActorEntry& ActorEntry : ActorEntries)
|
|
{
|
|
ActorEntry.ComponentEntries = ActorEntry.ComponentEntries.FilterByPredicate([&InSettings](const FComponentEntry& InEntry)
|
|
{
|
|
return InEntry.OriginalComponents.Num() >= InSettings.InstanceReplacementThreshold;
|
|
});
|
|
}
|
|
|
|
// Remove any empty actor entries
|
|
ActorEntries.RemoveAll([](const FActorEntry& ActorEntry){ return ActorEntry.ComponentEntries.Num() == 0; });
|
|
|
|
int32 TotalComponentCount = 0;
|
|
TArray<AActor*> ActorsToCleanUp;
|
|
for(FActorEntry& ActorEntry : ActorEntries)
|
|
{
|
|
for(const FComponentEntry& ComponentEntry : ActorEntry.ComponentEntries)
|
|
{
|
|
TotalComponentCount++;
|
|
for(UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents)
|
|
{
|
|
if(AActor* OriginalActor = OriginalComponent->GetOwner())
|
|
{
|
|
ActorsToCleanUp.AddUnique(OriginalActor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ActorEntries.Num() > 0)
|
|
{
|
|
if(OutResultsText != nullptr)
|
|
{
|
|
*OutResultsText = FText::Format(LOCTEXT("InstanceMergePredictedResults", "The current settings will result in {0} instanced static mesh components ({1} actors will be replaced)"), FText::AsNumber(TotalComponentCount), FText::AsNumber(ActorsToCleanUp.Num()));
|
|
}
|
|
|
|
if(bActuallyMerge)
|
|
{
|
|
// Create our actors
|
|
const FScopedTransaction Transaction(LOCTEXT("PlaceInstancedActors", "Place Instanced Actor(s)"));
|
|
Level->Modify(false);
|
|
|
|
FActorSpawnParameters Params;
|
|
Params.OverrideLevel = Level;
|
|
|
|
// We now have the set of component data we want to apply
|
|
for(FActorEntry& ActorEntry : ActorEntries)
|
|
{
|
|
ActorEntry.MergedActor = World->SpawnActor<AActor>(InSettings.ActorClassToUse.Get(), Params);
|
|
|
|
for(const FComponentEntry& ComponentEntry : ActorEntry.ComponentEntries)
|
|
{
|
|
UInstancedStaticMeshComponent* NewComponent = nullptr;
|
|
|
|
NewComponent = (UInstancedStaticMeshComponent*)ActorEntry.MergedActor->FindComponentByClass(InSettings.ISMComponentToUse.Get());
|
|
|
|
if (NewComponent && NewComponent->PerInstanceSMData.Num() > 0)
|
|
{
|
|
NewComponent = nullptr;
|
|
}
|
|
|
|
if (NewComponent == nullptr)
|
|
{
|
|
NewComponent = NewObject<UInstancedStaticMeshComponent>(ActorEntry.MergedActor, InSettings.ISMComponentToUse.Get(), NAME_None, RF_Transactional);
|
|
NewComponent->bHasPerInstanceHitProxies = true;
|
|
|
|
if (ActorEntry.MergedActor->GetRootComponent())
|
|
{
|
|
// Attach to root if we already have one
|
|
NewComponent->AttachToComponent(ActorEntry.MergedActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
}
|
|
else
|
|
{
|
|
// Make a new root if we dont have a root already
|
|
ActorEntry.MergedActor->SetRootComponent(NewComponent);
|
|
}
|
|
|
|
// Take 'instanced' ownership so it persists with this actor
|
|
ActorEntry.MergedActor->RemoveOwnedComponent(NewComponent);
|
|
NewComponent->CreationMethod = EComponentCreationMethod::Instance;
|
|
ActorEntry.MergedActor->AddOwnedComponent(NewComponent);
|
|
|
|
}
|
|
|
|
NewComponent->SetStaticMesh(ComponentEntry.StaticMesh);
|
|
for(int32 MaterialIndex = 0; MaterialIndex < ComponentEntry.Materials.Num(); ++MaterialIndex)
|
|
{
|
|
NewComponent->SetMaterial(MaterialIndex, ComponentEntry.Materials[MaterialIndex]);
|
|
}
|
|
NewComponent->SetReverseCulling(ComponentEntry.bReverseCulling);
|
|
NewComponent->SetCollisionProfileName(ComponentEntry.CollisionProfileName);
|
|
NewComponent->SetCollisionEnabled(ComponentEntry.CollisionEnabled);
|
|
NewComponent->SetMobility(EComponentMobility::Static);
|
|
|
|
FISMComponentBatcher ISMComponentBatcher;
|
|
ISMComponentBatcher.Append(ComponentEntry.OriginalComponents);
|
|
ISMComponentBatcher.InitComponent(NewComponent);
|
|
|
|
NewComponent->RegisterComponent();
|
|
}
|
|
|
|
World->UpdateCullDistanceVolumes(ActorEntry.MergedActor);
|
|
}
|
|
|
|
// Now clean up our original actors
|
|
for(AActor* ActorToCleanUp : ActorsToCleanUp)
|
|
{
|
|
if (bReplaceSourceActors)
|
|
{
|
|
ActorToCleanUp->Destroy();
|
|
}
|
|
else
|
|
{
|
|
ActorToCleanUp->Modify();
|
|
ActorToCleanUp->bIsEditorOnlyActor = true;
|
|
ActorToCleanUp->SetHidden(true);
|
|
ActorToCleanUp->bHiddenEd = true;
|
|
ActorToCleanUp->SetIsTemporarilyHiddenInEditor(true);
|
|
}
|
|
}
|
|
|
|
// pop a toast allowing selection
|
|
auto SelectActorsLambda = [ActorEntries]()
|
|
{
|
|
GEditor->GetSelectedActors()->Modify();
|
|
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
|
|
GEditor->SelectNone(false, true, false);
|
|
|
|
for(const FActorEntry& ActorEntry : ActorEntries)
|
|
{
|
|
GEditor->SelectActor(ActorEntry.MergedActor, true, false, true);
|
|
}
|
|
|
|
GEditor->GetSelectedActors()->EndBatchSelectOperation();
|
|
};
|
|
|
|
// Always change selection if we removed the source actors,
|
|
// Otherwise, allow selection change through notification
|
|
if (bReplaceSourceActors)
|
|
{
|
|
SelectActorsLambda();
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo NotificationInfo(FText::Format(LOCTEXT("CreatedInstancedActorsMessage", "Created {0} Instanced Actor(s)"), FText::AsNumber(ActorEntries.Num())));
|
|
NotificationInfo.Hyperlink = FSimpleDelegate::CreateLambda(SelectActorsLambda);
|
|
NotificationInfo.HyperlinkText = LOCTEXT("SelectActorsHyperlink", "Select Actors");
|
|
NotificationInfo.ExpireDuration = 5.0f;
|
|
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UMaterialInterface* FMeshMergeUtilities::CreateProxyMaterial(const FString &InBasePackageName, FString MergedAssetPackageName, UMaterialInterface* InBaseMaterial, UPackage* InOuter, const FMeshMergingSettings &InSettings, const FFlattenMaterial& OutMaterial, TArray<UObject *>& OutAssetsToSync, FMaterialUpdateContext* InMaterialUpdateContext) const
|
|
{
|
|
// Create merged material asset
|
|
FString MaterialAssetName;
|
|
FString MaterialPackageName;
|
|
if (InBasePackageName.IsEmpty())
|
|
{
|
|
MaterialAssetName = FPackageName::GetShortName(MergedAssetPackageName);
|
|
MaterialPackageName = FPackageName::GetLongPackagePath(MergedAssetPackageName) + TEXT("/");
|
|
}
|
|
else
|
|
{
|
|
MaterialAssetName = FPackageName::GetShortName(InBasePackageName);
|
|
MaterialPackageName = FPackageName::GetLongPackagePath(InBasePackageName) + TEXT("/");
|
|
}
|
|
|
|
UPackage* MaterialPackage = InOuter;
|
|
if (MaterialPackage == nullptr)
|
|
{
|
|
MaterialPackage = CreatePackage( *(MaterialPackageName + MaterialAssetName));
|
|
check(MaterialPackage);
|
|
MaterialPackage->FullyLoad();
|
|
MaterialPackage->Modify();
|
|
}
|
|
|
|
UMaterialInstanceConstant* MergedMaterial = FMaterialUtilities::CreateFlattenMaterialInstance(MaterialPackage, InSettings.MaterialSettings, InBaseMaterial, OutMaterial, MaterialPackageName, MaterialAssetName, OutAssetsToSync, InMaterialUpdateContext);
|
|
// Set material static lighting usage flag if project has static lighting enabled
|
|
if (IsStaticLightingAllowed())
|
|
{
|
|
MergedMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
|
|
}
|
|
|
|
return MergedMaterial;
|
|
}
|
|
|
|
void FMeshMergeUtilities::RetrievePhysicsData(const TArray<UPrimitiveComponent*>& ComponentsToMerge, TArray<FKAggregateGeom>& InOutPhysicsGeometry, UBodySetup*& OutBodySetupSource) const
|
|
{
|
|
InOutPhysicsGeometry.AddDefaulted(ComponentsToMerge.Num());
|
|
for (int32 ComponentIndex = 0, PhysicsGeometryIndex = 0; ComponentIndex < ComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
UPrimitiveComponent* PrimComp = ComponentsToMerge[ComponentIndex];
|
|
UBodySetup* BodySetup = nullptr;
|
|
FTransform ComponentToWorld = FTransform::Identity;
|
|
|
|
auto ExtractPhysicGeometry = [&BodySetup, &ComponentToWorld, &OutBodySetupSource, &InOutPhysicsGeometry, PrimComp](int32 PhysicsIndex) {
|
|
USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(PrimComp);
|
|
FMeshMergeHelpers::ExtractPhysicsGeometry(BodySetup, ComponentToWorld, SplineMeshComponent != nullptr, InOutPhysicsGeometry[PhysicsIndex]);
|
|
if (SplineMeshComponent)
|
|
{
|
|
FMeshMergeHelpers::PropagateSplineDeformationToPhysicsGeometry(SplineMeshComponent, InOutPhysicsGeometry[PhysicsIndex]);
|
|
}
|
|
|
|
// We will use first valid BodySetup as a source of physics settings
|
|
if (OutBodySetupSource == nullptr)
|
|
{
|
|
OutBodySetupSource = BodySetup;
|
|
}
|
|
};
|
|
|
|
if (UInstancedStaticMeshComponent* ISMComp = Cast<UInstancedStaticMeshComponent>(PrimComp))
|
|
{
|
|
const int32 NumberOfInstances = ISMComp->PerInstanceSMData.Num();
|
|
const UStaticMesh* SrcMesh = ISMComp->GetStaticMesh();
|
|
|
|
if (NumberOfInstances > 1)
|
|
{
|
|
InOutPhysicsGeometry.AddDefaulted(NumberOfInstances - 1);
|
|
}
|
|
|
|
if (SrcMesh)
|
|
{
|
|
BodySetup = SrcMesh->GetBodySetup();
|
|
}
|
|
|
|
for (const FInstancedStaticMeshInstanceData& InstanceData : ISMComp->PerInstanceSMData)
|
|
{
|
|
ComponentToWorld = FTransform(InstanceData.Transform) * ISMComp->GetComponentToWorld();
|
|
ExtractPhysicGeometry(PhysicsGeometryIndex++);
|
|
}
|
|
}
|
|
else if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(PrimComp))
|
|
{
|
|
UStaticMesh* SrcMesh = StaticMeshComp->GetStaticMesh();
|
|
if (SrcMesh)
|
|
{
|
|
BodySetup = SrcMesh->GetBodySetup();
|
|
}
|
|
ComponentToWorld = StaticMeshComp->GetComponentToWorld();
|
|
ExtractPhysicGeometry(PhysicsGeometryIndex++);
|
|
}
|
|
else if (UShapeComponent* ShapeComp = Cast<UShapeComponent>(PrimComp))
|
|
{
|
|
BodySetup = ShapeComp->GetBodySetup();
|
|
ComponentToWorld = ShapeComp->GetComponentToWorld();
|
|
ExtractPhysicGeometry(PhysicsGeometryIndex++);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE // "MeshMergeUtils"
|