You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Now extracted from sub levels and brought in the main map to be rendered as instanced static meshes. * Impostors meshes created during proxy creation. #rb jeanfrancois.dube, jurre.debaare #ROBOMERGE-SOURCE: CL 6675359 via CL 6675861 #ROBOMERGE-BOT: (vundefined-6665479) [CL 6675880 by sebastien lussier in Main branch]
3181 lines
120 KiB
C++
3181 lines
120 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshMergeUtilities.h"
|
|
|
|
#include "Engine/MapBuildDataRegistry.h"
|
|
#include "Engine/MeshMerging.h"
|
|
|
|
#include "MaterialOptions.h"
|
|
#include "IMaterialBakingModule.h"
|
|
|
|
#include "Misc/PackageName.h"
|
|
#include "MaterialUtilities.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 "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 "ProxyMaterialUtilities.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 "StaticMeshComponentAdapter.h"
|
|
#include "SkeletalMeshAdapter.h"
|
|
#include "StaticMeshAdapter.h"
|
|
#include "MeshMergeEditorExtensions.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 "MeshDescription.h"
|
|
#include "MeshAttributes.h"
|
|
#include "MeshAttributeArray.h"
|
|
#include "MeshDescriptionOperations.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
#define LOCTEXT_NAMESPACE "MeshMergeUtils"
|
|
|
|
DEFINE_LOG_CATEGORY(LogMeshMerging);
|
|
|
|
FMeshMergeUtilities::FMeshMergeUtilities()
|
|
{
|
|
Processor = new FProxyGenerationProcessor(this);
|
|
|
|
// Add callback for registering editor extensions with Skeletal/Static mesh editor
|
|
ModuleLoadedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddStatic(&FMeshMergeEditorExtensions::OnModulesChanged);
|
|
}
|
|
|
|
FMeshMergeUtilities::~FMeshMergeUtilities()
|
|
{
|
|
FModuleManager::Get().OnModulesChanged().Remove(ModuleLoadedDelegateHandle);
|
|
FMeshMergeEditorExtensions::RemoveExtenders();
|
|
}
|
|
|
|
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;
|
|
|
|
// LOD index, <original section index, unique section index>
|
|
TMultiMap<uint32, TPair<uint32, uint32>> UniqueSectionIndexPerLOD;
|
|
|
|
// Unique set of sections in mesh
|
|
TArray<FSectionInfo> UniqueSections;
|
|
|
|
TArray<FSectionInfo> Sections;
|
|
|
|
int32 NumLODs = Adapter->GetNumberOfLODs();
|
|
|
|
// Retrieve raw mesh data and unique sections
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
// Reset section for reuse
|
|
Sections.SetNum(0, false);
|
|
|
|
// Extract raw mesh data
|
|
const bool bProcessedLOD = MaterialOptions->LODIndices.Contains(LODIndex);
|
|
if (bProcessedLOD)
|
|
{
|
|
FMeshDescription& RawMesh = RawMeshLODs.Add(LODIndex);
|
|
UStaticMesh::RegisterMeshAttributes(RawMesh);
|
|
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.Add(LODIndex, TPair<uint32, uint32>(SectionIndex, UniqueIndex));
|
|
}
|
|
}
|
|
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
TMap<UMaterialInterface*, int32> MaterialIndices;
|
|
TMultiMap<uint32, uint32> SectionToMaterialMap;
|
|
// 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);
|
|
SectionToMaterialMap.Add(UniqueIndex, SectionIndex);
|
|
}
|
|
|
|
TArray<bool> bMaterialUsesVertexData;
|
|
DetermineMaterialVertexDataUsage(bMaterialUsesVertexData, UniqueMaterials, MaterialOptions);
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
TMultiMap< uint32, TPair<uint32, uint32>> OutputMaterialsMap;
|
|
for (int32 MaterialIndex = 0; MaterialIndex < UniqueMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
UMaterialInterface* Material = UniqueMaterials[MaterialIndex];
|
|
const bool bDoesMaterialUseVertexData = bMaterialUsesVertexData[MaterialIndex];
|
|
// Retrieve all sections using this material
|
|
TArray<uint32> SectionIndices;
|
|
SectionToMaterialMap.MultiFind(MaterialIndex, SectionIndices);
|
|
|
|
if (MaterialOptions->bUseMeshData)
|
|
{
|
|
for (const int32 LODIndex : MaterialOptions->LODIndices)
|
|
{
|
|
TArray<TPair<uint32, uint32>> IndexPairs;
|
|
UniqueSectionIndexPerLOD.MultiFind(LODIndex, IndexPairs);
|
|
|
|
FMeshData MeshSettings;
|
|
MeshSettings.RawMeshDescription = nullptr;
|
|
|
|
// Add material indices used for rendering out material
|
|
for (const TPair<uint32, uint32>& Pair : IndexPairs)
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
MeshSettings.MaterialIndices.Add(Pair.Key);
|
|
}
|
|
}
|
|
|
|
if (MeshSettings.MaterialIndices.Num())
|
|
{
|
|
// Retrieve raw mesh
|
|
MeshSettings.RawMeshDescription = RawMeshLODs.Find(LODIndex);
|
|
|
|
//Should not be using mesh data if there is no mesh
|
|
check(MeshSettings.RawMeshDescription);
|
|
|
|
MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
const bool bUseVertexColor = FMeshDescriptionOperations::HasVertexColor(*(MeshSettings.RawMeshDescription));
|
|
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.RawMeshDescription), MeshSettings.TextureCoordinateIndex);
|
|
const int32 LightMapUVIndex = Adapter->LightmapUVIndex();
|
|
|
|
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = MeshSettings.RawMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
if (bNeedsUniqueUVs && MeshSettings.TextureCoordinateIndex != LightMapUVIndex && VertexInstanceUVs.GetNumElements() > 0 && VertexInstanceUVs.GetNumIndices() > 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.Add(LODIndex, TPair<uint32, uint32>(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)
|
|
{
|
|
TArray<TPair<uint32, uint32>> IndexPairs;
|
|
UniqueSectionIndexPerLOD.MultiFind(LODIndex, IndexPairs);
|
|
for (const TPair<uint32, uint32>& Pair : IndexPairs)
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
MeshSettings.MaterialIndices.Add(Pair.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MeshSettings.MaterialIndices.Num())
|
|
{
|
|
MeshSettings.RawMeshDescription = 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)
|
|
{
|
|
if (!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)
|
|
{
|
|
TArray<TPair<uint32, uint32>> IndexPairs;
|
|
UniqueSectionIndexPerLOD.MultiFind(LODIndex, IndexPairs);
|
|
for (const TPair<uint32, uint32>& Pair : IndexPairs)
|
|
{
|
|
if (SectionIndices.Contains(Pair.Value))
|
|
{
|
|
/// For each original material index add an entry to the corresponding LOD and bake output index
|
|
OutputMaterialsMap.Add(LODIndex, TPair<uint32, uint32>(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, false);
|
|
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 MaterialIndex = 0; MaterialIndex < NewMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
TArray<uint32> SectionIndices;
|
|
SectionToMaterialMap.MultiFind(MaterialIndex, SectionIndices);
|
|
|
|
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
|
|
{
|
|
const bool bProcessedLOD = MaterialOptions->LODIndices.Contains(LODIndex);
|
|
if (!bProcessedLOD)
|
|
{
|
|
TArray<TPair<uint32, uint32>> IndexPairs;
|
|
UniqueSectionIndexPerLOD.MultiFind(LODIndex, IndexPairs);
|
|
|
|
for (TPair<uint32, uint32>& Pair : IndexPairs)
|
|
{
|
|
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)
|
|
{
|
|
TArray<TPair<uint32, uint32>> IndexPairs;
|
|
OutputMaterialsMap.MultiFind(LODIndex, IndexPairs);
|
|
|
|
// Key == original section index, Value == unique material index
|
|
for (auto Pair : IndexPairs)
|
|
{
|
|
int32 SetIndex = Adapter->GetMaterialIndex(LODIndex, Pair.Key);
|
|
if (!NonReplaceMaterialIndices.Contains(SetIndex))
|
|
{
|
|
Adapter->SetMaterial(SetIndex, NewMaterials[Pair.Value]);
|
|
}
|
|
else
|
|
{
|
|
const FSectionInfo& SectionInfo = UniqueSections[Pair.Key];
|
|
// 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
|
|
const int32 NewMaterialIndex = Adapter->AddMaterial(NewMaterials[Pair.Value]);
|
|
NewMaterialRemap.Add(Pair.Value, NewMaterialIndex);
|
|
Adapter->RemapMaterialIndex(LODIndex, Pair.Key, NewMaterialIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Adapter->UpdateUVChannelData();
|
|
GlobalMeshSettings.Empty();
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForComponent(USkeletalMeshComponent* SkeletalMeshComponent) const
|
|
{
|
|
// 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->SkeletalMesh->GetLODNum();
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
if (!Module.SetupMaterialBakeSettings(Objects, NumLODs))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Bake out materials for skeletal mesh
|
|
FSkeletalMeshComponentAdapter Adapter(SkeletalMeshComponent);
|
|
BakeMaterialsForComponent(Objects, &Adapter);
|
|
SkeletalMeshComponent->MarkRenderStateDirty();
|
|
}
|
|
|
|
void FMeshMergeUtilities::BakeMaterialsForComponent(UStaticMeshComponent* StaticMeshComponent) const
|
|
{
|
|
// 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
|
|
{
|
|
// 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
|
|
FStaticMeshAdapter Adapter(StaticMesh);
|
|
BakeMaterialsForComponent(Objects, &Adapter);
|
|
}
|
|
|
|
void FMeshMergeUtilities::DetermineMaterialVertexDataUsage(TArray<bool>& InOutMaterialUsesVertexData, const TArray<UMaterialInterface*>& UniqueMaterials, const UMaterialOptions* MaterialOptions) const
|
|
{
|
|
InOutMaterialUsesVertexData.SetNum(UniqueMaterials.Num());
|
|
for (int32 MaterialIndex = 0; MaterialIndex < UniqueMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
UMaterialInterface* Material = UniqueMaterials[MaterialIndex];
|
|
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)
|
|
{
|
|
InOutMaterialUsesVertexData[MaterialIndex] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = NewToOldProperty(SizePair.Key);
|
|
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);
|
|
}
|
|
}
|
|
|
|
EFlattenMaterialProperties FMeshMergeUtilities::NewToOldProperty(int32 NewProperty) const
|
|
{
|
|
const EFlattenMaterialProperties Remap[MP_Refraction] =
|
|
{
|
|
EFlattenMaterialProperties::Emissive,
|
|
EFlattenMaterialProperties::Opacity,
|
|
EFlattenMaterialProperties::OpacityMask,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::Diffuse,
|
|
EFlattenMaterialProperties::Metallic,
|
|
EFlattenMaterialProperties::Specular,
|
|
EFlattenMaterialProperties::Roughness,
|
|
EFlattenMaterialProperties::Normal,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::NumFlattenMaterialProperties,
|
|
EFlattenMaterialProperties::AmbientOcclusion
|
|
};
|
|
|
|
return Remap[NewProperty];
|
|
}
|
|
|
|
UMaterialOptions* FMeshMergeUtilities::PopulateMaterialOptions(const FMaterialProxySettings& MaterialSettings) const
|
|
{
|
|
UMaterialOptions* MaterialOptions = DuplicateObject(GetMutableDefault<UMaterialOptions>(), GetTransientPackage());
|
|
MaterialOptions->Properties.Empty();
|
|
MaterialOptions->TextureSize = MaterialSettings.TextureSize;
|
|
|
|
const bool bCustomSizes = MaterialSettings.TextureSizingType == TextureSizingType_UseManualOverrideTextureSize;
|
|
|
|
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_Metallic, Property);
|
|
if (MaterialSettings.bMetallicMap)
|
|
MaterialOptions->Properties.Add(Property);
|
|
|
|
PopulatePropertyEntry(MaterialSettings, MP_Normal, Property);
|
|
if (MaterialSettings.bNormalMap)
|
|
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_Metallic: return MaterialSettings.MetallicTextureSize;
|
|
case MP_Normal: return MaterialSettings.NormalTextureSize;
|
|
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_BaseColor: return HalfRes;
|
|
case MP_Specular: return QuarterRes;
|
|
case MP_Roughness: 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;
|
|
}
|
|
}
|
|
/** 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_Specular: return !MaterialSettings.bSpecularMap;
|
|
case MP_Roughness: return !MaterialSettings.bRoughnessMap;
|
|
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_Specular: return MaterialSettings.SpecularConstant;
|
|
case MP_Roughness: return MaterialSettings.RoughnessConstant;
|
|
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)
|
|
{
|
|
FImageUtils::ImageResize(SrcSize.X, SrcSize.Y, InOutImage, DesiredSize.X, DesiredSize.Y, OutImage, bLinearSpace);
|
|
}
|
|
Exchange(InOutImage, OutImage);
|
|
return DesiredSize;
|
|
}
|
|
|
|
return SrcSize;
|
|
}
|
|
|
|
void FMeshMergeUtilities::MergeFlattenedMaterials(TArray<struct FFlattenMaterial>& InMaterialList, int32 InGutter, FFlattenMaterial& OutMergedMaterial, TArray<FUVOffsetScalePair>& OutUVTransforms) const
|
|
{
|
|
OutUVTransforms.Reserve(InMaterialList.Num());
|
|
|
|
// Fill output UV transforms with invalid values
|
|
for (auto Material : InMaterialList)
|
|
{
|
|
|
|
// Invalid UV transform
|
|
FUVOffsetScalePair UVTransform;
|
|
UVTransform.Key = FVector2D::ZeroVector;
|
|
UVTransform.Value = FVector2D::ZeroVector;
|
|
OutUVTransforms.Add(UVTransform);
|
|
}
|
|
|
|
const int32 AtlasGridSize = FMath::CeilToInt(FMath::Sqrt(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
|
|
{
|
|
OutUVTransforms.AddZeroed(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((Index)* MaterialsPerThread);
|
|
const int32 EndIndex = FMath::Min(FMath::CeilToInt((Index + 1) * 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;
|
|
}
|
|
|
|
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
|
|
{
|
|
UMaterial* BaseMaterial = LoadObject<UMaterial>(NULL, TEXT("/Engine/EngineMaterials/BaseFlattenMaterial.BaseFlattenMaterial"), NULL, LOAD_None, NULL);
|
|
check(BaseMaterial);
|
|
CreateProxyMesh(InActors, InMeshProxySettings, BaseMaterial, 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
|
|
{
|
|
UMaterial* BaseMaterial = LoadObject<UMaterial>(NULL, TEXT("/Engine/EngineMaterials/BaseFlattenMaterial.BaseFlattenMaterial"), NULL, LOAD_None, NULL);
|
|
check(BaseMaterial);
|
|
CreateProxyMesh(InStaticMeshComps, InMeshProxySettings, BaseMaterial, 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<UStaticMeshComponent>(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
|
|
{
|
|
// 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, Log, TEXT("No automatic mesh merging module available"));
|
|
return;
|
|
}
|
|
|
|
// Check that the delegate has a func-ptr bound to it
|
|
if (!InProxyCreatedDelegate.IsBound())
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("Invalid (unbound) delegate for returning generated proxy mesh"));
|
|
return;
|
|
}
|
|
|
|
TArray<UStaticMeshComponent*> ComponentsToMerge = InComponentsToMerge;
|
|
|
|
// Remove anything non-regular or non-spline static mesh components
|
|
ComponentsToMerge.RemoveAll([](UStaticMeshComponent* Val)
|
|
{
|
|
if (Val->GetClass() != UStaticMeshComponent::StaticClass() && Val->GetClass() != UInstancedStaticMeshComponent::StaticClass() && !Val->IsA(USplineMeshComponent::StaticClass()))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Val->GetStaticMesh() == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
// 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;
|
|
static const int32 ProxyMeshTargetLODLevel = 0;
|
|
|
|
FBoxSphereBounds EstimatedBounds(ForceInitToZero);
|
|
for (const UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
|
|
{
|
|
EstimatedBounds = EstimatedBounds + StaticMeshComponent->Bounds;
|
|
}
|
|
|
|
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);
|
|
|
|
SlowTask.EnterProgressFrame(5.0f, LOCTEXT("CreateProxyMesh_CollectingMeshes", "Collecting Input Static Meshes"));
|
|
|
|
// Mesh / LOD index
|
|
TMap<uint32, FMeshDescription*> RawMeshLODs;
|
|
|
|
TArray<FMeshDescription*> RawMeshData;
|
|
|
|
// LOD index, <original section index, unique section index>
|
|
TMultiMap<uint32, TPair<uint32, uint32>> UniqueSectionIndexPerLOD;
|
|
|
|
// Unique set of sections in mesh
|
|
TArray<FSectionInfo> UniqueSections;
|
|
TArray<FSectionInfo> Sections;
|
|
TMultiMap<uint32, uint32> SectionToMesh;
|
|
|
|
int32 SummedLightmapPixels = 0;
|
|
|
|
TArray<const UStaticMeshComponent*> ImposterMeshComponents;
|
|
|
|
for (const UStaticMeshComponent* StaticMeshComponent : ComponentsToMerge)
|
|
{
|
|
int32 NumInstances = 1;
|
|
if (StaticMeshComponent->bUseMaxLODAsImposter)
|
|
{
|
|
ImposterMeshComponents.Add(StaticMeshComponent);
|
|
}
|
|
else
|
|
{
|
|
const int32 ScreenSizeBasedLODLevel = Utilities->GetLODLevelForScreenSize(StaticMeshComponent, Utilities->CalculateScreenSizeFromDrawDistance(StaticMeshComponent->Bounds.SphereRadius, ProjectionMatrix, EstimatedDistance));
|
|
const int32 LODIndex = InMeshProxySettings.bCalculateCorrectLODModel ? ScreenSizeBasedLODLevel : 0;
|
|
static const bool bPropagateVertexColours = true;
|
|
|
|
// Retrieve mesh data in FMeshDescription form
|
|
FMeshDescription* RawMesh = new FMeshDescription();
|
|
UStaticMesh::RegisterMeshAttributes(*RawMesh);
|
|
FMeshMergeHelpers::RetrieveMesh(StaticMeshComponent, LODIndex, *RawMesh, bPropagateVertexColours);
|
|
const int32 MeshIndex = RawMeshData.Add(RawMesh);
|
|
|
|
// Reset section array for reuse
|
|
Sections.SetNum(0, false);
|
|
// Extract sections for given LOD index from the mesh
|
|
FMeshMergeHelpers::ExtractSections(StaticMeshComponent, LODIndex, Sections);
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = Sections[SectionIndex];
|
|
const int32 UniqueIndex = UniqueSections.AddUnique(Section);
|
|
UniqueSectionIndexPerLOD.Add(MeshIndex, TPair<uint32, uint32>(UniqueIndex, Section.MaterialIndex));
|
|
|
|
SectionToMesh.Add(UniqueIndex, MeshIndex);
|
|
}
|
|
|
|
// If the component is an ISMC then we need to duplicate the vertex data
|
|
if(StaticMeshComponent->GetClass() == UInstancedStaticMeshComponent::StaticClass())
|
|
{
|
|
const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent);
|
|
FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, *RawMesh, Sections);
|
|
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);
|
|
}
|
|
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
TMultiMap<uint32, uint32> SectionToMaterialMap;
|
|
for (int32 SectionIndex = 0; SectionIndex < UniqueSections.Num(); ++SectionIndex)
|
|
{
|
|
FSectionInfo& Section = UniqueSections[SectionIndex];
|
|
const int32 UniqueIndex = UniqueMaterials.AddUnique(Section.Material);
|
|
SectionToMaterialMap.Add(UniqueIndex, SectionIndex);
|
|
}
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
|
|
UMaterialOptions* Options = PopulateMaterialOptions(InMeshProxySettings.MaterialSettings);
|
|
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;
|
|
for (int32 MaterialIndex = 0; MaterialIndex < UniqueMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
UMaterialInterface* Material = UniqueMaterials[MaterialIndex];
|
|
|
|
TArray<uint32> SectionIndices;
|
|
SectionToMaterialMap.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 (uint32 MeshIndex : MeshIndices)
|
|
{
|
|
FMeshData MeshSettings;
|
|
// Retrieve raw mesh
|
|
FMeshDescription* MeshDescription = RawMeshData[MeshIndex];
|
|
MeshSettings.RawMeshDescription = MeshDescription;
|
|
|
|
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshSettings.RawMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
// If we already have lightmap uvs generated or the lightmap coordinate index != 0 and available we can reuse those instead of having to generate new ones
|
|
if (InMeshProxySettings.bReuseMeshLightmapUVs
|
|
&& (ComponentsToMerge[MeshIndex]->GetStaticMesh()->SourceModels[0].BuildSettings.bGenerateLightmapUVs
|
|
|| (ComponentsToMerge[MeshIndex]->GetStaticMesh()->LightMapCoordinateIndex != 0 && VertexInstanceUVs.GetNumElements() > 0 && VertexInstanceUVs.GetNumIndices() > ComponentsToMerge[MeshIndex]->GetStaticMesh()->LightMapCoordinateIndex)))
|
|
{
|
|
MeshSettings.CustomTextureCoordinates.Reset(VertexInstanceUVs.GetNumElements());
|
|
int32 LightMapCoordinateIndex = ComponentsToMerge[MeshIndex]->GetStaticMesh()->LightMapCoordinateIndex;
|
|
for (const FVertexInstanceID VertexInstanceID : MeshSettings.RawMeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
MeshSettings.CustomTextureCoordinates.Add(VertexInstanceUVs.Get(VertexInstanceID, LightMapCoordinateIndex));
|
|
}
|
|
ScaleTextureCoordinatesToBox(FBox2D(FVector2D::ZeroVector, FVector2D(1, 1)), MeshSettings.CustomTextureCoordinates);
|
|
}
|
|
else
|
|
{
|
|
// Generate unique UVs for mesh (should only be done if needed)
|
|
FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(*MeshDescription, Options->TextureSize.GetMax(), false, MeshSettings.CustomTextureCoordinates);
|
|
ScaleTextureCoordinatesToBox(FBox2D(FVector2D::ZeroVector, FVector2D(1, 1)), MeshSettings.CustomTextureCoordinates);
|
|
}
|
|
|
|
MeshSettings.TextureCoordinateBox = FBox2D(MeshSettings.CustomTextureCoordinates);
|
|
|
|
// Section index is a unique one so we need to map it to the mesh's equivalent(s)
|
|
TArray<TPair<uint32, uint32>> UniqueToMeshSectionIndices;
|
|
UniqueSectionIndexPerLOD.MultiFind(MeshIndex, UniqueToMeshSectionIndices);
|
|
for (const TPair<uint32, uint32> IndexPair : UniqueToMeshSectionIndices)
|
|
{
|
|
if (IndexPair.Key == SectionIndex)
|
|
{
|
|
MeshSettings.MaterialIndices.Add(IndexPair.Value);
|
|
}
|
|
}
|
|
|
|
// Retrieve lightmap for usage of lightmap data
|
|
const UStaticMeshComponent* StaticMeshComponent = ComponentsToMerge[MeshIndex];
|
|
if (StaticMeshComponent->LODData.IsValidIndex(0))
|
|
{
|
|
const FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[0];
|
|
const FMeshMapBuildData* MeshMapBuildData = StaticMeshComponent->GetMeshMapBuildData(ComponentLODInfo);
|
|
if (MeshMapBuildData)
|
|
{
|
|
MeshSettings.LightMap = MeshMapBuildData->LightMap;
|
|
MeshSettings.LightMapIndex = StaticMeshComponent->GetStaticMesh()->LightMapCoordinateIndex;
|
|
}
|
|
}
|
|
|
|
// For each original material index add an entry to the corresponding LOD and bake output index
|
|
for (int32 Index : MeshSettings.MaterialIndices)
|
|
{
|
|
OutputMaterialsMap.Add(MeshIndex, TPair<uint32, uint32>(Index, GlobalMeshSettings.Num()));
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MeshSettings);
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add simple bake entry
|
|
FMeshData MeshSettings;
|
|
MeshSettings.RawMeshDescription = 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>> UniqueToMeshSectionIndices;
|
|
UniqueSectionIndexPerLOD.MultiFind(MeshIndex, UniqueToMeshSectionIndices);
|
|
for (const TPair<uint32, uint32> IndexPair : UniqueToMeshSectionIndices)
|
|
{
|
|
if (IndexPair.Key == SectionIndex)
|
|
{
|
|
OutputMaterialsMap.Add(MeshIndex, TPair<uint32, uint32>(IndexPair.Value, GlobalMeshSettings.Num()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GlobalMeshSettings.Add(MeshSettings);
|
|
GlobalMaterialSettings.Add(MaterialSettings);
|
|
}
|
|
}
|
|
|
|
TArray<FFlattenMaterial> FlattenedMaterials;
|
|
IMaterialBakingModule& MaterialBakingModule = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
|
|
auto MaterialFlattenLambda =
|
|
[this, &Options, &GlobalMeshSettings, &GlobalMaterialSettings, &RawMeshData, &OutputMaterialsMap, &MaterialBakingModule](TArray<FFlattenMaterial>& FlattenedMaterialArray)
|
|
{
|
|
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;
|
|
|
|
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, false);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
ConvertOutputToFlatMaterials(BakeOutputs, GlobalMaterialSettings, FlattenedMaterialArray);
|
|
|
|
// 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 < RawMeshData.Num(); ++MeshIndex)
|
|
{
|
|
FMeshDescription& RawMesh = *RawMeshData[MeshIndex];
|
|
|
|
TArray<TPair<uint32, uint32>> SectionAndOutputIndices;
|
|
OutputMaterialsMap.MultiFind(MeshIndex, SectionAndOutputIndices);
|
|
//Make sure the section index are in the correct order
|
|
SectionAndOutputIndices.Sort([](const TPair<uint32, uint32>& A, const TPair<uint32, uint32>& B) { return (A.Key < B.Key); });
|
|
|
|
TArray<int32> Remap;
|
|
TArray<int32> UniqueMaterialIndexes;
|
|
// Reorder loops
|
|
for (const TPair<uint32, uint32>& IndexPair : SectionAndOutputIndices)
|
|
{
|
|
//We are not using the IndexPair.Key since we want to keep the polygon group
|
|
//We instead find the section index by looking at unique IndexPair.Value
|
|
const int32 NewIndex = IndexPair.Value;
|
|
|
|
const int32 SectionIndex = UniqueMaterialIndexes.AddUnique(NewIndex);
|
|
if (Remap.Num() < (SectionIndex + 1))
|
|
{
|
|
Remap.SetNum(SectionIndex + 1);
|
|
}
|
|
|
|
Remap[SectionIndex] = NewIndex;
|
|
}
|
|
|
|
TMap<FPolygonGroupID, FPolygonGroupID> RemapPolygonGroup;
|
|
for (const FPolygonGroupID& PolygonGroupID : RawMesh.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));
|
|
}
|
|
FMeshDescriptionOperations::RemapPolygonGroups(RawMesh, 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 = ComponentsToMerge;
|
|
Data->StaticMeshComponents.RemoveAll([&](UStaticMeshComponent* Component) { return ImposterMeshComponents.Contains(Component); });
|
|
Data->BaseMaterial = InBaseMaterial;
|
|
|
|
// Lightmap resolution
|
|
if (InMeshProxySettings.bComputeLightMapResolution)
|
|
{
|
|
Data->InProxySettings.LightMapResolution = FMath::CeilToInt(FMath::Sqrt(SummedLightmapPixels));
|
|
}
|
|
|
|
// Add this proxy job to map
|
|
Processor->AddProxyJob(InGuid, Data);
|
|
|
|
// We are only using LOD level 0 (ProxyMeshTargetLODLevel)
|
|
TArray<FMeshMergeData> MergeDataEntries;
|
|
for (int32 Index = 0; Index < RawMeshData.Num(); ++Index)
|
|
{
|
|
FMeshMergeData MergeData;
|
|
MergeData.SourceStaticMesh = ComponentsToMerge[Index]->GetStaticMesh();
|
|
MergeData.RawMesh = RawMeshData[Index];
|
|
MergeData.bIsClippingMesh = false;
|
|
|
|
FMeshMergeHelpers::CalculateTextureCoordinateBoundsForRawMesh(*MergeData.RawMesh, MergeData.TexCoordBounds);
|
|
|
|
FMeshData* MeshData = GlobalMeshSettings.FindByPredicate([&](const FMeshData& Entry)
|
|
{
|
|
return Entry.RawMeshDescription == MergeData.RawMesh && (Entry.CustomTextureCoordinates.Num() || Entry.TextureCoordinateIndex != 0);
|
|
});
|
|
|
|
if (MeshData)
|
|
{
|
|
if (MeshData->CustomTextureCoordinates.Num())
|
|
{
|
|
MergeData.NewUVs = MeshData->CustomTextureCoordinates;
|
|
}
|
|
else
|
|
{
|
|
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshData->RawMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
MergeData.NewUVs.Reset(MeshData->RawMeshDescription->VertexInstances().Num());
|
|
for (const FVertexInstanceID VertexInstanceID : MeshData->RawMeshDescription->VertexInstances().GetElementIDs())
|
|
{
|
|
MergeData.NewUVs.Add(VertexInstanceUVs.Get(VertexInstanceID, MeshData->TextureCoordinateIndex));
|
|
}
|
|
}
|
|
MergeData.TexCoordBounds[0] = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
}
|
|
MergeDataEntries.Add(MergeData);
|
|
}
|
|
|
|
// Populate landscape clipping geometry
|
|
for (FMeshDescription* RawMesh : CullingRawMeshes)
|
|
{
|
|
FMeshMergeData ClipData;
|
|
ClipData.bIsClippingMesh = true;
|
|
ClipData.RawMesh = RawMesh;
|
|
MergeDataEntries.Add(ClipData);
|
|
}
|
|
|
|
SlowTask.EnterProgressFrame(50.0f, LOCTEXT("CreateProxyMesh_GenerateProxy", "Generating Proxy Mesh"));
|
|
|
|
// 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
|
|
}
|
|
|
|
// Clean up the CullingRawMeshes
|
|
for (FMeshDescription* RawMesh : CullingRawMeshes)
|
|
{
|
|
delete RawMesh;
|
|
}
|
|
}
|
|
|
|
|
|
bool FMeshMergeUtilities::IsValidBaseMaterial(const UMaterialInterface* InBaseMaterial, bool bShowToaster) const
|
|
{
|
|
if (InBaseMaterial != nullptr)
|
|
{
|
|
TArray<FGuid> ParameterIds;
|
|
TArray<FString> MissingParameters;
|
|
auto NameCheckLambda = [&MissingParameters](const TArray<FMaterialParameterInfo>& InCheck, const TArray<FName>& InRequired)
|
|
{
|
|
for (const FName& Name : InRequired)
|
|
{
|
|
if (!InCheck.ContainsByPredicate([Name](const FMaterialParameterInfo& ParamInfo) { return (ParamInfo.Name == Name); }))
|
|
{
|
|
MissingParameters.Add(Name.ToString());
|
|
}
|
|
}
|
|
};
|
|
|
|
TArray<FMaterialParameterInfo> TextureParameterInfos;
|
|
TArray<FName> RequiredTextureNames = { TEXT("DiffuseTexture"), TEXT("NormalTexture"), TEXT("PackedTexture"), TEXT("MetallicTexture"), TEXT("SpecularTexture"), TEXT("RoughnessTexture"), TEXT("EmissiveTexture"), TEXT("OpacityTexture"), TEXT("OpacityMaskTexture"), TEXT("AmbientOcclusionTexture") };
|
|
InBaseMaterial->GetAllTextureParameterInfo(TextureParameterInfos, ParameterIds);
|
|
NameCheckLambda(TextureParameterInfos, RequiredTextureNames);
|
|
|
|
TArray<FMaterialParameterInfo> ScalarParameterInfos;
|
|
TArray<FName> RequiredScalarNames = { TEXT("MetallicConst"), TEXT("SpecularConst"), TEXT("RoughnessConst"), TEXT("OpacityConst"), TEXT("OpacityMaskConst"), TEXT("AmbientOcclusionConst"), TEXT("EmissiveScale") };
|
|
InBaseMaterial->GetAllScalarParameterInfo(ScalarParameterInfos, ParameterIds);
|
|
NameCheckLambda(ScalarParameterInfos, RequiredScalarNames);
|
|
|
|
TArray<FMaterialParameterInfo> VectorParameterInfos;
|
|
TArray<FName> RequiredVectorNames = { TEXT("DiffuseConst"), TEXT("EmissiveConst") };
|
|
InBaseMaterial->GetAllVectorParameterInfo(VectorParameterInfos, ParameterIds);
|
|
NameCheckLambda(VectorParameterInfos, RequiredVectorNames);
|
|
|
|
TArray<FMaterialParameterInfo> StaticSwitchParameterInfos;
|
|
TArray<FName> RequiredSwitchNames = { TEXT("UseDiffuse"), TEXT("PackMetallic"), TEXT("PackSpecular"), TEXT("PackRoughness"),TEXT("UseMetallic"), TEXT("UseSpecular"), TEXT("UseRoughness"), TEXT("UseEmissive"), TEXT("UseOpacity"), TEXT("UseOpacityMask"), TEXT("UseAmbientOcclusion") };
|
|
InBaseMaterial->GetAllStaticSwitchParameterInfo(StaticSwitchParameterInfos, ParameterIds);
|
|
NameCheckLambda(StaticSwitchParameterInfos, RequiredSwitchNames);
|
|
|
|
if (MissingParameters.Num() > 0)
|
|
{
|
|
FString MissingNamesString;
|
|
for (const FString& Name : MissingParameters)
|
|
{
|
|
if (!MissingNamesString.IsEmpty())
|
|
{
|
|
MissingNamesString += ", ";
|
|
MissingNamesString += Name;
|
|
}
|
|
else
|
|
{
|
|
MissingNamesString += Name;
|
|
}
|
|
}
|
|
#if WITH_EDITOR
|
|
if (bShowToaster)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("MaterialName"), FText::FromString(InBaseMaterial->GetName()));
|
|
FText ErrorMessage = FText::Format(LOCTEXT("UHierarchicalLODSettings_PostEditChangeProperty", "Material {MaterialName} is missing required Material Parameters (check log for details)"), Arguments);
|
|
FNotificationInfo Info(ErrorMessage);
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
}
|
|
|
|
UE_LOG(LogMeshMerging, Error, TEXT("Material %s is missing required Material Parameters %s, resetting to default."), *InBaseMaterial->GetName(), *MissingNamesString);
|
|
#endif // WITH_EDITOR
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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, false);
|
|
|
|
// Extract sections for given LOD index from the mesh
|
|
Adapter.RetrieveMeshSections(LODIndex, Sections);
|
|
|
|
TMap<FPolygonGroupID, FPolygonGroupID> RemapPolygonGroup;
|
|
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, Section.MaterialIndex, UniqueIndex);
|
|
DataTracker.AddMaterialSlotName(Section.Material, Section.MaterialSlotName);
|
|
|
|
if (!bMergeMaterialData)
|
|
{
|
|
FMeshDescriptionOperations::SwapPolygonPolygonGroup(RawMesh, UniqueIndex, Section.StartIndex, Section.EndIndex, false);
|
|
}
|
|
else
|
|
{
|
|
RemapPolygonGroup.Add(FPolygonGroupID(SectionIndex), FPolygonGroupID(Section.MaterialIndex));
|
|
}
|
|
}
|
|
|
|
if (bMergeMaterialData)
|
|
{
|
|
FMeshDescriptionOperations::RemapPolygonGroups(RawMesh, RemapPolygonGroup);
|
|
}
|
|
|
|
//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->GetClass() == UInstancedStaticMeshComponent::StaticClass())
|
|
{
|
|
const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(Component);
|
|
FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, RawMesh, Sections);
|
|
}
|
|
|
|
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()->LightMapCoordinateIndex);
|
|
}
|
|
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
|
|
{
|
|
// 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)
|
|
{
|
|
if(MeshComponent->bUseMaxLODAsImposter && 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)
|
|
{
|
|
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->bUseMaxLODAsImposter)
|
|
{
|
|
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
|
|
{
|
|
// 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->bUseMaxLODAsImposter && !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);
|
|
}
|
|
}
|
|
|
|
DataTracker.ProcessRawMeshes();
|
|
|
|
// Retrieve physics data
|
|
UBodySetup* BodySetupSource = nullptr;
|
|
TArray<FKAggregateGeom> PhysicsGeometry;
|
|
if (InSettings.bMergePhysicsData)
|
|
{
|
|
ExtractPhysicsDataFromComponents(ComponentsToMerge, PhysicsGeometry, BodySetupSource);
|
|
}
|
|
|
|
// Find all unique materials and remap section to unique materials
|
|
TArray<UMaterialInterface*> UniqueMaterials;
|
|
TMap<UMaterialInterface*, int32> MaterialIndices;
|
|
TMultiMap<uint32, uint32> SectionToMaterialMap;
|
|
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]);
|
|
|
|
// Store off usage of unique material by unique sections
|
|
SectionToMaterialMap.Add(UniqueIndex, SectionIndex);
|
|
}
|
|
|
|
// For each unique material calculate how 'important' they are
|
|
TArray<float> MaterialImportanceValues;
|
|
FMaterialUtilities::DetermineMaterialImportance(UniqueMaterials, MaterialImportanceValues);
|
|
|
|
// If the user wants to merge materials into a single one
|
|
if (bMergeMaterialData)
|
|
{
|
|
UMaterialOptions* MaterialOptions = PopulateMaterialOptions(InSettings.MaterialSettings);
|
|
// Check each material to see if the shader actually uses vertex data and collect flags
|
|
TArray<bool> bMaterialUsesVertexData;
|
|
DetermineMaterialVertexDataUsage(bMaterialUsesVertexData, UniqueMaterials, MaterialOptions);
|
|
|
|
TArray<FMeshData> GlobalMeshSettings;
|
|
TArray<FMaterialData> GlobalMaterialSettings;
|
|
TArray<float> SectionMaterialImportanceValues;
|
|
|
|
TMultiMap< FMeshLODKey, MaterialRemapPair > OutputMaterialsMap;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
TMap<UMaterialInterface*, int32> MaterialToDefaultMeshData;
|
|
|
|
for (TConstRawMeshIterator RawMeshIterator = DataTracker.GetConstRawMeshIterator(); RawMeshIterator; ++RawMeshIterator)
|
|
{
|
|
const FMeshLODKey& Key = RawMeshIterator.Key();
|
|
const FMeshDescription& RawMesh = RawMeshIterator.Value();
|
|
const bool bRequiresUniqueUVs = DataTracker.DoesMeshLODRequireUniqueUVs(Key);
|
|
UStaticMeshComponent* Component = StaticMeshComponentsToMerge[Key.GetMeshIndex()];
|
|
|
|
// Retrieve all sections and materials for key
|
|
TArray<SectionRemapPair> SectionRemapPairs;
|
|
DataTracker.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(CollapsedMaterialMap.FindChecked(DataTracker.GetMaterialForSectionIndex(UniqueIndex)));
|
|
SectionIndices.Add(SectionIndex);
|
|
}
|
|
|
|
// Cache unique texture coordinates
|
|
TArray<FVector2D> UniqueTextureCoordinates;
|
|
|
|
for (TPair<UMaterialInterface*, TArray<int32>>& MaterialSectionIndexPair : MaterialAndSectionIndices)
|
|
{
|
|
UMaterialInterface* Material = MaterialSectionIndexPair.Key;
|
|
const int32 MaterialIndex = UniqueMaterials.IndexOfByKey(Material);
|
|
const TArray<int32>& SectionIndices = MaterialSectionIndexPair.Value;
|
|
const bool bDoesMaterialUseVertexData = bMaterialUsesVertexData[MaterialIndex];
|
|
|
|
FMaterialData MaterialData;
|
|
MaterialData.Material = CollapsedMaterialMap.FindChecked(Material);
|
|
MaterialData.PropertySizes = PropertySizes;
|
|
|
|
FMeshData MeshData;
|
|
MeshData.Mesh = Key.GetMesh();
|
|
MeshData.VertexColorHash = Key.GetVertexColorHash();
|
|
MeshData.bMirrored = Component->GetComponentTransform().GetDeterminant() < 0.0f;
|
|
int32 MeshDataIndex = 0;
|
|
|
|
if (InSettings.bUseVertexDataForBakingMaterial && (bDoesMaterialUseVertexData || bRequiresUniqueUVs))
|
|
{
|
|
MeshData.RawMeshDescription = DataTracker.GetRawMeshPtr(Key);
|
|
|
|
// 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.RawMeshDescription->VertexInstances().Num() > 0)
|
|
{
|
|
// Check if there are lightmap uvs available?
|
|
const int32 LightMapUVIndex = StaticMeshComponentsToMerge[Key.GetMeshIndex()]->GetStaticMesh()->LightMapCoordinateIndex;
|
|
|
|
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshData.RawMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
if (InSettings.bReuseMeshLightmapUVs && VertexInstanceUVs.GetNumElements() > 0 && VertexInstanceUVs.GetNumIndices() > LightMapUVIndex)
|
|
{
|
|
MeshData.TextureCoordinateIndex = LightMapUVIndex;
|
|
}
|
|
else
|
|
{
|
|
if (!UniqueTextureCoordinates.Num())
|
|
{
|
|
FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(*MeshData.RawMeshDescription, MaterialOptions->TextureSize.GetMax(), false, UniqueTextureCoordinates);
|
|
ScaleTextureCoordinatesToBox(FBox2D(FVector2D::ZeroVector, FVector2D(1, 1)), UniqueTextureCoordinates);
|
|
}
|
|
MeshData.CustomTextureCoordinates = UniqueTextureCoordinates;
|
|
}
|
|
}
|
|
|
|
MeshData.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
MeshData.MaterialIndices = SectionIndices;
|
|
MeshDataIndex = GlobalMeshSettings.Num();
|
|
|
|
Adapters[Key.GetMeshIndex()].ApplySettings(Key.GetLODIndex(), MeshData);
|
|
|
|
int32 ExistingMeshDataIndex = INDEX_NONE;
|
|
|
|
auto MaterialsAreEquivalent = [&InSettings](const UMaterialInterface* Material0, const UMaterialInterface* Material1)
|
|
{
|
|
if(InSettings.bMergeEquivalentMaterials)
|
|
{
|
|
return FMaterialKey(Material0) == FMaterialKey(Material1);
|
|
}
|
|
else
|
|
{
|
|
return Material0 == Material1;
|
|
}
|
|
};
|
|
|
|
// Find any existing materials
|
|
for(int32 GlobalMaterialSettingsIndex = 0; GlobalMaterialSettingsIndex < GlobalMaterialSettings.Num(); ++GlobalMaterialSettingsIndex)
|
|
{
|
|
const FMaterialData& ExistingMaterialData = GlobalMaterialSettings[GlobalMaterialSettingsIndex];
|
|
// Compare materials (note this assumes property sizes match!)
|
|
if(MaterialsAreEquivalent(ExistingMaterialData.Material, MaterialData.Material))
|
|
{
|
|
// materials match, so check the corresponding mesh data
|
|
const FMeshData& ExistingMeshData = GlobalMeshSettings[GlobalMaterialSettingsIndex];
|
|
bool bMatchesMesh = (ExistingMeshData.Mesh == MeshData.Mesh &&
|
|
ExistingMeshData.MaterialIndices == MeshData.MaterialIndices &&
|
|
ExistingMeshData.bMirrored == MeshData.bMirrored &&
|
|
ExistingMeshData.VertexColorHash == MeshData.VertexColorHash);
|
|
if(bMatchesMesh)
|
|
{
|
|
MeshDataIndex = ExistingMeshDataIndex = GlobalMaterialSettingsIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ExistingMeshDataIndex == INDEX_NONE)
|
|
{
|
|
GlobalMeshSettings.Add(MeshData);
|
|
GlobalMaterialSettings.Add(MaterialData);
|
|
SectionMaterialImportanceValues.Add(MaterialImportanceValues[MaterialIndex]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MeshData.RawMeshDescription = nullptr;
|
|
MeshData.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f));
|
|
|
|
// This prevents baking out the same material multiple times, which would be wasteful when it does not use vertex data anyway
|
|
const bool bPreviouslyAdded = MaterialToDefaultMeshData.Contains(Material);
|
|
int32& DefaultMeshDataIndex = MaterialToDefaultMeshData.FindOrAdd(Material);
|
|
|
|
if (!bPreviouslyAdded)
|
|
{
|
|
DefaultMeshDataIndex = GlobalMeshSettings.Num();
|
|
GlobalMeshSettings.Add(MeshData);
|
|
GlobalMaterialSettings.Add(MaterialData);
|
|
SectionMaterialImportanceValues.Add(MaterialImportanceValues[MaterialIndex]);
|
|
}
|
|
|
|
MeshDataIndex = DefaultMeshDataIndex;
|
|
}
|
|
|
|
for (const uint32& OriginalSectionIndex : SectionIndices)
|
|
{
|
|
OutputMaterialsMap.Add(Key, MaterialRemapPair(OriginalSectionIndex, MeshDataIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 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 = !bMergeAllLODs && !InSettings.bReuseMeshLightmapUVs;
|
|
if(bGloballyRemapUVs)
|
|
{
|
|
TArray<FMeshDescription> MergedRawMeshes;
|
|
CreateMergedRawMeshes(DataTracker, InSettings, StaticMeshComponentsToMerge, UniqueMaterials, CollapsedMaterialMap, OutputMaterialsMap, false, false, MergedAssetPivot, MergedRawMeshes);
|
|
|
|
// Create texture coords for the merged mesh
|
|
TArray<FVector2D> GlobalTextureCoordinates;
|
|
FMeshDescriptionOperations::GenerateUniqueUVsForStaticMesh(MergedRawMeshes[0], MaterialOptions->TextureSize.GetMax(), true, GlobalTextureCoordinates);
|
|
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 = DataTracker.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.RawMeshDescription == &RawMesh)
|
|
{
|
|
MeshData.CustomTextureCoordinates = UniqueTextureCoordinates;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dont smear borders as we will copy back non-pink pixels
|
|
for(FMaterialData& MaterialData : GlobalMaterialSettings)
|
|
{
|
|
MaterialData.bPerformBorderSmear = false;
|
|
}
|
|
}
|
|
|
|
TArray<FBakeOutput> BakeOutputs;
|
|
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
|
|
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, false);
|
|
ConstantData[0] = FLinearColor(Entry.ConstantValue, Entry.ConstantValue, Entry.ConstantValue).ToFColor(true);
|
|
for (FBakeOutput& Ouput : BakeOutputs)
|
|
{
|
|
Ouput.PropertyData.Add(Entry.Property, ConstantData);
|
|
Ouput.PropertySizes.Add(Entry.Property, ConstantSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FFlattenMaterial> FlattenedMaterials;
|
|
ConvertOutputToFlatMaterials(BakeOutputs, GlobalMaterialSettings, FlattenedMaterials);
|
|
|
|
if(!bGloballyRemapUVs)
|
|
{
|
|
// Try to optimize materials where possible
|
|
for (FFlattenMaterial& InMaterial : FlattenedMaterials)
|
|
{
|
|
FMaterialUtilities::OptimizeFlattenMaterial(InMaterial);
|
|
}
|
|
}
|
|
|
|
FFlattenMaterial OutMaterial;
|
|
for (const FPropertyEntry& Entry : MaterialOptions->Properties)
|
|
{
|
|
if (Entry.Property != MP_MAX)
|
|
{
|
|
EFlattenMaterialProperties OldProperty = NewToOldProperty(Entry.Property);
|
|
OutMaterial.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, OutMaterial, UVTransforms);
|
|
}
|
|
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, OutMaterial, UVTransforms);
|
|
}
|
|
else
|
|
{
|
|
MergeFlattenedMaterials(FlattenedMaterials, InSettings.GutterSize, OutMaterial, UVTransforms);
|
|
}
|
|
}
|
|
|
|
// Adjust UVs
|
|
for (int32 ComponentIndex = 0; ComponentIndex < StaticMeshComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
TArray<uint32> ProcessedMaterials;
|
|
for (TPair<FMeshLODKey, MaterialRemapPair>& MappingPair : OutputMaterialsMap)
|
|
{
|
|
if (MappingPair.Key.GetMeshIndex() == ComponentIndex && !ProcessedMaterials.Contains(MappingPair.Value.Key))
|
|
{
|
|
const int32 LODIndex = MappingPair.Key.GetLODIndex();
|
|
// Found component entry
|
|
|
|
// Retrieve raw mesh data for this component and lod pair
|
|
FMeshDescription* RawMesh = DataTracker.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<FVector2D> VertexInstanceUVs = RawMesh->VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
int32 NumUVChannel = FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_MESH_TEXTURE_COORDS);
|
|
for (int32 UVChannelIdx = 0; UVChannelIdx < NumUVChannel; ++UVChannelIdx)
|
|
{
|
|
int32 VertexIndex = 0;
|
|
for (FVertexInstanceID VertexInstanceID : RawMesh->VertexInstances().GetElementIDs())
|
|
{
|
|
FVector2D UV = 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 = 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, UV * UVTransform.Value + UVTransform.Key);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
VertexIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TRawMeshIterator Iterator = DataTracker.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));
|
|
}
|
|
FMeshDescriptionOperations::RemapPolygonGroups(RawMesh, RemapPolygonGroups);
|
|
}
|
|
|
|
UMaterialInterface* MergedMaterial = CreateProxyMaterial(InBasePackageName, MergedAssetPackageName, InBaseMaterial, InOuter, InSettings, OutMaterial, OutAssetsToSync);
|
|
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);
|
|
}
|
|
}
|
|
|
|
TArray<FMeshDescription> MergedRawMeshes;
|
|
TMultiMap<FMeshLODKey, MaterialRemapPair> OutputMaterialsMap;
|
|
CreateMergedRawMeshes(DataTracker, InSettings, StaticMeshComponentsToMerge, UniqueMaterials, CollapsedMaterialMap, OutputMaterialsMap, bMergeAllLODs, bMergeMaterialData, MergedAssetPivot, 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.GetPolygonGroupPolygons(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];
|
|
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.IndexOfByKey(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)
|
|
{
|
|
return TempChannel;
|
|
}
|
|
else
|
|
{
|
|
// Output warning message
|
|
UE_LOG(LogMeshMerging, Log, TEXT("Failed to find available lightmap uv channel"));
|
|
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}();
|
|
|
|
//
|
|
//Create merged mesh asset
|
|
//
|
|
{
|
|
FString AssetName;
|
|
FString PackageName;
|
|
if (InBasePackageName.IsEmpty())
|
|
{
|
|
AssetName = TEXT("SM_MERGED_") + FPackageName::GetShortName(MergedAssetPackageName);
|
|
PackageName = FPackageName::GetLongPackagePath(MergedAssetPackageName) + TEXT("/") + AssetName;
|
|
}
|
|
else
|
|
{
|
|
AssetName = FPackageName::GetShortName(InBasePackageName);
|
|
PackageName = InBasePackageName;
|
|
}
|
|
|
|
UPackage* Package = InOuter;
|
|
if (Package == nullptr)
|
|
{
|
|
Package = CreatePackage(NULL, *PackageName);
|
|
check(Package);
|
|
Package->FullyLoad();
|
|
Package->Modify();
|
|
}
|
|
|
|
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->LightingGuid = FGuid::NewGuid();
|
|
if (InSettings.bGenerateLightMapUV)
|
|
{
|
|
StaticMesh->LightMapResolution = InSettings.TargetLightMapResolution;
|
|
StaticMesh->LightMapCoordinateIndex = LightMapUVChannel;
|
|
}
|
|
|
|
TArray<UMaterialInterface*> ImposterMaterials;
|
|
FBox ImposterBounds(EForceInit::ForceInit);
|
|
for (int32 LODIndex = 0; LODIndex < MergedRawMeshes.Num(); ++LODIndex)
|
|
{
|
|
FMeshDescription& MergedMeshLOD = MergedRawMeshes[LODIndex];
|
|
if (MergedMeshLOD.Vertices().Num() > 0)
|
|
{
|
|
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.bGenerateLightmapUVs = InSettings.bGenerateLightMapUV;
|
|
SrcModel.BuildSettings.MinLightmapResolution = InSettings.bComputedLightMapResolution ? DataTracker.GetLightMapDimension() : InSettings.TargetLightMapResolution;
|
|
SrcModel.BuildSettings.SrcLightmapIndex = 0;
|
|
SrcModel.BuildSettings.DstLightmapIndex = LightMapUVChannel;
|
|
if(!InSettings.bAllowDistanceField)
|
|
{
|
|
SrcModel.BuildSettings.DistanceFieldResolutionScale = 0.0f;
|
|
}
|
|
|
|
const bool bContainsImposters = ImposterComponents.Num() > 0;
|
|
if (bContainsImposters)
|
|
{
|
|
// Merge imposter meshes to rawmesh
|
|
FMeshMergeHelpers::MergeImpostersToRawMesh(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);
|
|
*MeshDescription = MergedMeshLOD;
|
|
StaticMesh->CommitMeshDescription(LODIndex);
|
|
}
|
|
}
|
|
|
|
auto IsMaterialImportedNameUnique = [&StaticMesh](FName ImportedMaterialSlotName)
|
|
{
|
|
for (const FStaticMaterial& StaticMaterial : StaticMesh->StaticMaterials)
|
|
{
|
|
#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()))
|
|
{
|
|
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->StaticMaterials.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->StaticMaterials.Add(FStaticMaterial(ImposterMaterial, MaterialSlotName));
|
|
}
|
|
|
|
if (InSettings.bMergePhysicsData)
|
|
{
|
|
StaticMesh->CreateBodySetup();
|
|
if (BodySetupSource)
|
|
{
|
|
StaticMesh->BodySetup->CopyBodyPropertiesFrom(BodySetupSource);
|
|
}
|
|
|
|
StaticMesh->BodySetup->AggGeom = FKAggregateGeom();
|
|
// Copy collision from the source meshes
|
|
for (const FKAggregateGeom& Geom : PhysicsGeometry)
|
|
{
|
|
StaticMesh->BodySetup->AddCollisionFrom(Geom);
|
|
}
|
|
|
|
// Bake rotation into verts of convex hulls, so they scale correctly after rotation
|
|
for (FKConvexElem& ConvexElem : StaticMesh->BodySetup->AggGeom.ConvexElems)
|
|
{
|
|
ConvexElem.BakeTransformToVerts();
|
|
}
|
|
}
|
|
|
|
StaticMesh->SectionInfoMap.CopyFrom(SectionInfoMap);
|
|
StaticMesh->OriginalSectionInfoMap.CopyFrom(SectionInfoMap);
|
|
|
|
//Set the Imported version before calling the build
|
|
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
|
|
StaticMesh->LightMapResolution = InSettings.bComputedLightMapResolution ? DataTracker.GetLightMapDimension() : InSettings.TargetLightMapResolution;
|
|
|
|
#if WITH_EDITOR
|
|
//If we are running the automation test
|
|
if (GIsAutomationTesting)
|
|
{
|
|
StaticMesh->BuildCacheAutomationTestGuid = FGuid::NewGuid();
|
|
}
|
|
#endif
|
|
StaticMesh->Build(bSilent);
|
|
|
|
if (ImposterBounds.IsValid)
|
|
{
|
|
const FBox StaticMeshBox = StaticMesh->GetBoundingBox();
|
|
const FBox CombinedBox = StaticMeshBox + ImposterBounds;
|
|
StaticMesh->PositiveBoundsExtension = (CombinedBox.Max - StaticMeshBox.Max);
|
|
StaticMesh->NegativeBoundsExtension = (StaticMeshBox.Min - CombinedBox.Min);
|
|
StaticMesh->CalculateExtendedBounds();
|
|
}
|
|
|
|
StaticMesh->PostEditChange();
|
|
|
|
OutAssetsToSync.Add(StaticMesh);
|
|
OutMergedActorLocation = MergedAssetPivot;
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::ExtractImposterToRawMesh(const UStaticMeshComponent* InImposterComponent, FMeshDescription& InImposterMesh) const
|
|
{
|
|
check(InImposterComponent->bUseMaxLODAsImposter);
|
|
FMeshMergeHelpers::ExtractImposterToRawMesh(InImposterComponent, InImposterMesh);
|
|
}
|
|
|
|
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
|
|
{
|
|
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];
|
|
UStaticMesh::RegisterMeshAttributes(MergedMesh);
|
|
|
|
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());
|
|
|
|
FMeshDescriptionOperations::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;
|
|
FMeshDescriptionOperations::AppendMeshDescription(*RawMeshPtr, MergedMesh, AppendSettings);
|
|
}
|
|
}
|
|
|
|
//Cleanup the empty material to avoid empty section later
|
|
TArray<FPolygonGroupID> PolygonGroupToRemove;
|
|
for (FPolygonGroupID PolygonGroupID : MergedMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
if (MergedMesh.GetPolygonGroupPolygons(PolygonGroupID).Num() < 1)
|
|
{
|
|
PolygonGroupToRemove.Add(PolygonGroupID);
|
|
|
|
}
|
|
}
|
|
for (FPolygonGroupID PolygonGroupID : PolygonGroupToRemove)
|
|
{
|
|
MergedMesh.DeletePolygonGroup(PolygonGroupID);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutMergedRawMeshes.AddZeroed(1);
|
|
FMeshDescription& MergedMesh = OutMergedRawMeshes.Last();
|
|
UStaticMesh::RegisterMeshAttributes(MergedMesh);
|
|
|
|
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());
|
|
|
|
FMeshDescriptionOperations::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;
|
|
FMeshDescriptionOperations::AppendMeshDescription(*RawMeshPtr, MergedMesh, AppendSettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (IMeshMergeExtension* Extension : MeshMergeExtensions)
|
|
{
|
|
Extension->OnCreatedMergedRawMeshes(InStaticMeshComponentsToMerge, InDataTracker, OutMergedRawMeshes);
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::MergeComponentsToInstances(const TArray<UPrimitiveComponent*>& ComponentsToMerge, UWorld* World, ULevel* Level, const FMeshInstancingSettings& InSettings, bool bActuallyMerge /*= true*/, 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();
|
|
|
|
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)
|
|
{
|
|
auto AddInstancedStaticMeshComponent = [](AActor* InActor)
|
|
{
|
|
// Check if we have a usable (empty) ISMC first
|
|
if(UInstancedStaticMeshComponent* ExistingComponent = InActor->FindComponentByClass<UInstancedStaticMeshComponent>())
|
|
{
|
|
if(ExistingComponent->PerInstanceSMData.Num() == 0)
|
|
{
|
|
return ExistingComponent;
|
|
}
|
|
}
|
|
|
|
UInstancedStaticMeshComponent* NewComponent = NewObject<UInstancedStaticMeshComponent>(InActor);
|
|
if(InActor->GetRootComponent())
|
|
{
|
|
// Attach to root if we already have one
|
|
NewComponent->AttachToComponent(InActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
}
|
|
else
|
|
{
|
|
// Make a new root if we dont have a root already
|
|
InActor->SetRootComponent(NewComponent);
|
|
}
|
|
|
|
// Take 'instanced' ownership so it persists with this actor
|
|
InActor->RemoveOwnedComponent(NewComponent);
|
|
NewComponent->CreationMethod = EComponentCreationMethod::Instance;
|
|
InActor->AddOwnedComponent(NewComponent);
|
|
return NewComponent;
|
|
};
|
|
|
|
UInstancedStaticMeshComponent* NewComponent = AddInstancedStaticMeshComponent(ActorEntry.MergedActor);
|
|
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);
|
|
for(UStaticMeshComponent* OriginalComponent : ComponentEntry.OriginalComponents)
|
|
{
|
|
NewComponent->AddInstance(OriginalComponent->GetComponentTransform());
|
|
}
|
|
|
|
NewComponent->RegisterComponent();
|
|
}
|
|
|
|
World->UpdateCullDistanceVolumes(ActorEntry.MergedActor);
|
|
}
|
|
|
|
// Now clean up our original actors
|
|
for(AActor* ActorToCleanUp : ActorsToCleanUp)
|
|
{
|
|
if(InSettings.MeshReplacementMethod == EMeshInstancingReplacementMethod::RemoveOriginalActors)
|
|
{
|
|
ActorToCleanUp->Destroy();
|
|
}
|
|
else if(InSettings.MeshReplacementMethod == EMeshInstancingReplacementMethod::KeepOriginalActorsAsEditorOnly)
|
|
{
|
|
ActorToCleanUp->Modify();
|
|
ActorToCleanUp->bIsEditorOnlyActor = true;
|
|
ActorToCleanUp->bHidden = 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();
|
|
};
|
|
|
|
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, FFlattenMaterial OutMaterial, TArray<UObject *>& OutAssetsToSync) 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(nullptr, *(MaterialPackageName + MaterialAssetName));
|
|
check(MaterialPackage);
|
|
MaterialPackage->FullyLoad();
|
|
MaterialPackage->Modify();
|
|
}
|
|
|
|
UMaterialInstanceConstant* MergedMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(MaterialPackage, InSettings.MaterialSettings, InBaseMaterial, OutMaterial, MaterialPackageName, MaterialAssetName, OutAssetsToSync);
|
|
// Set material static lighting usage flag if project has static lighting enabled
|
|
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
|
|
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
|
|
if (bAllowStaticLighting)
|
|
{
|
|
MergedMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
|
|
}
|
|
|
|
return MergedMaterial;
|
|
}
|
|
|
|
void FMeshMergeUtilities::ExtractPhysicsDataFromComponents(const TArray<UPrimitiveComponent*>& ComponentsToMerge, TArray<FKAggregateGeom>& InOutPhysicsGeometry, UBodySetup*& OutBodySetupSource) const
|
|
{
|
|
InOutPhysicsGeometry.AddDefaulted(ComponentsToMerge.Num());
|
|
for (int32 ComponentIndex = 0; ComponentIndex < ComponentsToMerge.Num(); ++ComponentIndex)
|
|
{
|
|
UPrimitiveComponent* PrimComp = ComponentsToMerge[ComponentIndex];
|
|
UBodySetup* BodySetup = nullptr;
|
|
FTransform ComponentToWorld = FTransform::Identity;
|
|
|
|
if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(PrimComp))
|
|
{
|
|
UStaticMesh* SrcMesh = StaticMeshComp->GetStaticMesh();
|
|
if (SrcMesh)
|
|
{
|
|
BodySetup = SrcMesh->BodySetup;
|
|
}
|
|
ComponentToWorld = StaticMeshComp->GetComponentToWorld();
|
|
}
|
|
else if (UShapeComponent* ShapeComp = Cast<UShapeComponent>(PrimComp))
|
|
{
|
|
BodySetup = ShapeComp->GetBodySetup();
|
|
ComponentToWorld = ShapeComp->GetComponentToWorld();
|
|
}
|
|
|
|
USplineMeshComponent* SplineMeshComponent = Cast<USplineMeshComponent>(PrimComp);
|
|
FMeshMergeHelpers::ExtractPhysicsGeometry(BodySetup, ComponentToWorld, SplineMeshComponent != nullptr, InOutPhysicsGeometry[ComponentIndex]);
|
|
if (SplineMeshComponent)
|
|
{
|
|
FMeshMergeHelpers::PropagateSplineDeformationToPhysicsGeometry(SplineMeshComponent, InOutPhysicsGeometry[ComponentIndex]);
|
|
}
|
|
|
|
// We will use first valid BodySetup as a source of physics settings
|
|
if (OutBodySetupSource == nullptr)
|
|
{
|
|
OutBodySetupSource = BodySetup;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshMergeUtilities::ScaleTextureCoordinatesToBox(const FBox2D& Box, TArray<FVector2D>& InOutTextureCoordinates) const
|
|
{
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE // "MeshMergeUtils"
|