Files
ryan schmidt db4634dfb9 GeometryScripting: improve support for handling StaticMesh/DynamicMesh LOD and Material aspects
UE::AssetUtils::GetStaticMeshLODMaterialListBySection() now returns the slot name for each Material
BP function GetSectionMaterialListFromStaticMesh() now returns slot names. Fixed issue in this function where LODIndex was clamped to Render LODs, not SourceModel LODs. This function now also works for RenderData LODs. In CopyMeshToStaticMesh, for RenderData LODs, the MaterialID is set to the MaterialIndex referenced by each mesh section. So in this case in GetSectionMaterialListFromStaticMesh, the Asset Material List is returned directly. This is not really a "per Section Material List' as the function name implies, however it allows this function to provide comparable behavior between SourceModel and RenderData LODs.

Add new BP function RemapToNewMaterialIDsByMaterial() which figures out MaterialID remapping based on original and new Material lists (assuming MaterialID indexes into original Material list)
Add BP functions GetNumStaticMeshLODsOfType() and CheckStaticMeshHasAvailableLOD(), these utilities are useful for dealing w/ multi-LOD assets

#rb jimmy.andrews

[CL 29052212 by ryan schmidt in ue5-main branch]
2023-10-24 15:57:31 -04:00

206 lines
5.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AssetUtils/StaticMeshMaterialUtil.h"
#include "Engine/StaticMesh.h"
#include "Materials/MaterialInterface.h"
#include "MeshDescription.h"
#include "StaticMeshAttributes.h"
using namespace UE::AssetUtils;
bool UE::AssetUtils::GetStaticMeshLODAssetMaterials(
UStaticMesh* StaticMeshAsset,
int32 LODIndex,
FStaticMeshLODMaterialSetInfo& MaterialInfoOut)
{
if (!StaticMeshAsset)
{
return false;
}
#if WITH_EDITOR
if (StaticMeshAsset->IsSourceModelValid(LODIndex) == false)
{
return false;
}
// Need to access the mesh here because # Sections == # PolygonGroups and this info doesn't seem to be anywhere else.
// Otherwise do not use the mesh in this function (would be nice to avoid this call if possible)
const FMeshDescription* SourceMesh = StaticMeshAsset->GetMeshDescription(LODIndex);
int32 NumSections = SourceMesh->PolygonGroups().Num();
TArray<FStaticMaterial> StaticMaterials = StaticMeshAsset->GetStaticMaterials();
MaterialInfoOut.MaterialSlots.Reset();
for (FStaticMaterial Mat : StaticMaterials)
{
MaterialInfoOut.MaterialSlots.Add( FStaticMeshMaterialSlot{ Mat.MaterialInterface, Mat.MaterialSlotName } );
}
// This is complicated. A UStaticMesh has N MaterialSlots and each LOD has M Sections.
// Each Section can have any MaterialSlot assigned to it, ie it is not necessarily 1-1 or in-order.
// The SectionInfoMap is a TMap that will contain the SectionIndex-to-SlotIndex mapping
// *if* the mapping is not (SectionIndex == SlotIndex), or has ever been edited.
// So if the SectionIndex is not found in the SectionInfoMap, then it should be used as the SlotIndex directly.
const FMeshSectionInfoMap& SectionInfoMap = StaticMeshAsset->GetSectionInfoMap();
MaterialInfoOut.LODIndex = LODIndex;
MaterialInfoOut.NumSections = NumSections;
MaterialInfoOut.SectionSlotIndexes.SetNum(MaterialInfoOut.NumSections);
MaterialInfoOut.SectionMaterials.SetNum(MaterialInfoOut.NumSections);
for (int32 SectionIndex = 0; SectionIndex < MaterialInfoOut.NumSections; ++SectionIndex)
{
MaterialInfoOut.SectionSlotIndexes[SectionIndex] = -1;
MaterialInfoOut.SectionMaterials[SectionIndex] = nullptr;
if (SectionInfoMap.IsValidSection(LODIndex, SectionIndex) == false)
{
// did not find this section
if ( StaticMaterials.IsValidIndex(SectionIndex) )
{
MaterialInfoOut.SectionSlotIndexes[SectionIndex] = SectionIndex;
MaterialInfoOut.SectionMaterials[SectionIndex] = MaterialInfoOut.MaterialSlots[SectionIndex].Material;
}
else
{
ensure(false); // material list is broken? use default material.
}
}
else
{
FMeshSectionInfo SectionInfo = SectionInfoMap.Get(LODIndex, SectionIndex);
if ( StaticMaterials.IsValidIndex(SectionInfo.MaterialIndex) )
{
MaterialInfoOut.SectionSlotIndexes[SectionIndex] = SectionInfo.MaterialIndex;
MaterialInfoOut.SectionMaterials[SectionIndex] = MaterialInfoOut.MaterialSlots[SectionInfo.MaterialIndex].Material;
}
else
{
ensure(false); // this is *not* supposed to be able to happen! SectionMap is broken...
}
}
}
return true;
#else
// TODO: how would we handle this for runtime static mesh?
return false;
#endif
}
bool UE::AssetUtils::GetStaticMeshLODMaterialListBySection(
UStaticMesh* StaticMeshAsset,
int32 LODIndex,
TArray<UMaterialInterface*>& MaterialListOut,
TArray<int32>& MaterialIndexOut,
TArray<FName>& MaterialSlotNameOut)
{
#if WITH_EDITOR
// need valid MeshDescription in Editor path
if (StaticMeshAsset->IsMeshDescriptionValid(LODIndex) == false)
{
return false;
}
#endif
FStaticMeshLODMaterialSetInfo MaterialSetInfo;
if (GetStaticMeshLODAssetMaterials(StaticMeshAsset, LODIndex, MaterialSetInfo) == false)
{
return false;
}
#if WITH_EDITOR
const FMeshDescription* SourceMesh = StaticMeshAsset->GetMeshDescription(LODIndex);
// # Sections == # PolygonGroups
int32 NumPolygonGroups = SourceMesh->PolygonGroups().Num();
MaterialListOut.Reset();
MaterialIndexOut.Reset();
for (int32 k = 0; k < NumPolygonGroups; ++k)
{
int32 UseSlotIndex = -1;
if (k < MaterialSetInfo.SectionSlotIndexes.Num())
{
UseSlotIndex = MaterialSetInfo.SectionSlotIndexes[k];
}
else if (MaterialSetInfo.SectionSlotIndexes.Num() > 0)
{
UseSlotIndex = 0;
}
if (UseSlotIndex >= 0)
{
MaterialIndexOut.Add(UseSlotIndex);
MaterialListOut.Add(MaterialSetInfo.MaterialSlots[UseSlotIndex].Material);
MaterialSlotNameOut.Add(MaterialSetInfo.MaterialSlots[UseSlotIndex].SlotName);
}
else
{
MaterialIndexOut.Add(-1);
MaterialListOut.Add(nullptr);
MaterialSlotNameOut.Add(NAME_None);
}
}
return true;
#else
// TODO: how would we handle this for runtime static mesh?
return false;
#endif
}
FName UE::AssetUtils::GenerateNewMaterialSlotName(
const TArray<FStaticMaterial>& ExistingMaterials,
UMaterialInterface* SlotMaterial,
int32 NewSlotIndex)
{
FString MaterialName = (SlotMaterial) ? SlotMaterial->GetName() : TEXT("Material");
FName BaseName(MaterialName);
bool bFound = false;
for (const FStaticMaterial& Mat : ExistingMaterials)
{
if (Mat.MaterialSlotName == BaseName || Mat.ImportedMaterialSlotName == BaseName)
{
bFound = true;
break;
}
}
if (bFound == false && SlotMaterial != nullptr)
{
return BaseName;
}
bFound = true;
while (bFound)
{
bFound = false;
BaseName = FName(FString::Printf(TEXT("%s_%d"), *MaterialName, NewSlotIndex++));
for (const FStaticMaterial& Mat : ExistingMaterials)
{
if (Mat.MaterialSlotName == BaseName || Mat.ImportedMaterialSlotName == BaseName)
{
bFound = true;
break;
}
}
}
return BaseName;
}