Files
UnrealEngine/Engine/Source/Developer/SkeletalMeshUtilitiesCommon/Private/StaticToSkeletalMeshConverter.cpp
2025-05-18 13:04:45 +08:00

642 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "StaticToSkeletalMeshConverter.h"
#if WITH_EDITOR
#include "Animation/Skeleton.h"
#include "EditorFramework/AssetImportData.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SkinnedAssetCommon.h"
#include "Engine/StaticMesh.h"
#include "InterchangeHelper.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "LODUtilities.h"
#include "MeshDescription.h"
#include "MeshUtilities.h"
#include "Misc/Guid.h"
#include "Modules/ModuleManager.h"
#include "ReferenceSkeleton.h"
#include "Rendering/SkeletalMeshLODImporterData.h"
#include "Rendering/SkeletalMeshModel.h"
#include "SkeletalMeshAttributes.h"
DEFINE_LOG_CATEGORY_STATIC(LogStaticToSkeletalMeshConverter, Log, All);
static const FName RootBoneName("Root");
static const TCHAR* JointBaseName(TEXT("Joint"));
bool FStaticToSkeletalMeshConverter::InitializeSkeletonFromStaticMesh(
USkeleton* InSkeleton,
const UStaticMesh* InStaticMesh,
const FVector& InRelativeRootPosition
)
{
if (!ensure(InSkeleton))
{
return false;
}
if (InSkeleton->GetReferenceSkeleton().GetNum() != 0)
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Skeleton '%s' is not empty"), *InSkeleton->GetPathName());
return false;
}
if (!ensure(InStaticMesh))
{
return false;
}
const FBox Bounds = InStaticMesh->GetBoundingBox();
const FVector RootPosition = Bounds.Min + (Bounds.Max - Bounds.Min) * InRelativeRootPosition;
FTransform RootTransform(FTransform::Identity);
RootTransform.SetTranslation(RootPosition);
FReferenceSkeletonModifier Modifier(InSkeleton);
Modifier.Add(FMeshBoneInfo(RootBoneName, RootBoneName.ToString(), INDEX_NONE), RootTransform);
return true;
}
bool FStaticToSkeletalMeshConverter::InitializeSkeletonFromStaticMesh(
USkeleton* InSkeleton,
const UStaticMesh* InStaticMesh,
const FVector& InRelativeRootPosition,
const FVector& InRelativeEndEffectorPosition,
const int32 InIntermediaryJointCount
)
{
if (!ensure(InSkeleton))
{
return false;
}
if (InSkeleton->GetReferenceSkeleton().GetNum() != 0)
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Skeleton '%s' is not empty"), *InSkeleton->GetPathName());
return false;
}
if (!ensure(InStaticMesh))
{
return false;
}
if (FMath::IsNearlyZero(FVector::DistSquared(InRelativeEndEffectorPosition, InRelativeRootPosition)))
{
return InitializeSkeletonFromStaticMesh(InSkeleton, InStaticMesh, InRelativeRootPosition);
}
const FBox Bounds = InStaticMesh->GetBoundingBox();
const FVector RootPosition = Bounds.Min + (Bounds.Max - Bounds.Min) * InRelativeRootPosition;
const FVector EndEffectorPosition = Bounds.Min + (Bounds.Max - Bounds.Min) * InRelativeEndEffectorPosition;
// Find a rough rotation we can use
const FQuat Rotation = FQuat::FindBetweenVectors(FVector::ZAxisVector, EndEffectorPosition - RootPosition).GetNormalized();
FTransform ParentTransform(FTransform::Identity);
ParentTransform.SetTranslation(RootPosition);
ParentTransform.SetRotation(Rotation);
FReferenceSkeletonModifier Modifier(InSkeleton);
Modifier.Add(FMeshBoneInfo(RootBoneName, RootBoneName.ToString(), INDEX_NONE), ParentTransform);
for (int32 JointIndex = 0; JointIndex <= InIntermediaryJointCount; JointIndex++)
{
const double T = (JointIndex + 1.0) / (InIntermediaryJointCount + 2.0);
FTransform PointTransform(ParentTransform);
PointTransform.SetTranslation(RootPosition + (EndEffectorPosition - RootPosition) * T);
FString JointName = FString::Printf(TEXT("%s_%d"), JointBaseName, JointIndex + 1);
Modifier.Add(FMeshBoneInfo(FName(JointName), JointName, JointIndex), PointTransform * ParentTransform.Inverse());
ParentTransform = PointTransform;
}
return true;
}
static void CopyBuildSettings(
const FMeshBuildSettings& InStaticMeshBuildSettings,
FSkeletalMeshBuildSettings& OutSkeletalMeshBuildSettings
)
{
OutSkeletalMeshBuildSettings.bRecomputeNormals = InStaticMeshBuildSettings.bRecomputeNormals;
OutSkeletalMeshBuildSettings.bRecomputeTangents = InStaticMeshBuildSettings.bRecomputeTangents;
OutSkeletalMeshBuildSettings.bUseMikkTSpace = InStaticMeshBuildSettings.bUseMikkTSpace;
OutSkeletalMeshBuildSettings.bComputeWeightedNormals = InStaticMeshBuildSettings.bComputeWeightedNormals;
OutSkeletalMeshBuildSettings.bRemoveDegenerates = InStaticMeshBuildSettings.bRemoveDegenerates;
OutSkeletalMeshBuildSettings.bUseHighPrecisionTangentBasis = InStaticMeshBuildSettings.bUseHighPrecisionTangentBasis;
OutSkeletalMeshBuildSettings.bUseFullPrecisionUVs = InStaticMeshBuildSettings.bUseFullPrecisionUVs;
OutSkeletalMeshBuildSettings.bUseBackwardsCompatibleF16TruncUVs = InStaticMeshBuildSettings.bUseBackwardsCompatibleF16TruncUVs;
// The rest we leave at defaults.
}
static SkeletalMeshOptimizationImportance ConvertOptimizationImportance(
EMeshFeatureImportance::Type InStaticMeshImportance)
{
switch(InStaticMeshImportance)
{
default:
case EMeshFeatureImportance::Off: return SMOI_Highest;
case EMeshFeatureImportance::Lowest: return SMOI_Lowest;
case EMeshFeatureImportance::Low: return SMOI_Low;
case EMeshFeatureImportance::Normal: return SMOI_Normal;
case EMeshFeatureImportance::High: return SMOI_High;
case EMeshFeatureImportance::Highest: return SMOI_Highest;
}
}
static void CopyReductionSettings(
const FMeshReductionSettings& InStaticMeshReductionSettings,
FSkeletalMeshOptimizationSettings& OutSkeletalMeshReductionSettings
)
{
// Copy the reduction settings as closely as we can.
OutSkeletalMeshReductionSettings.NumOfTrianglesPercentage = InStaticMeshReductionSettings.PercentTriangles;
OutSkeletalMeshReductionSettings.NumOfVertPercentage = InStaticMeshReductionSettings.PercentVertices;
OutSkeletalMeshReductionSettings.WeldingThreshold = InStaticMeshReductionSettings.WeldingThreshold;
OutSkeletalMeshReductionSettings.NormalsThreshold = InStaticMeshReductionSettings.HardAngleThreshold;
OutSkeletalMeshReductionSettings.bRecalcNormals = InStaticMeshReductionSettings.bRecalculateNormals;
OutSkeletalMeshReductionSettings.BaseLOD = InStaticMeshReductionSettings.BaseLODModel;
OutSkeletalMeshReductionSettings.SilhouetteImportance = ConvertOptimizationImportance(InStaticMeshReductionSettings.SilhouetteImportance);
OutSkeletalMeshReductionSettings.TextureImportance = ConvertOptimizationImportance(InStaticMeshReductionSettings.TextureImportance);
OutSkeletalMeshReductionSettings.ShadingImportance = ConvertOptimizationImportance(InStaticMeshReductionSettings.ShadingImportance);
switch(InStaticMeshReductionSettings.TerminationCriterion)
{
case EStaticMeshReductionTerimationCriterion::Triangles:
OutSkeletalMeshReductionSettings.TerminationCriterion = SMTC_NumOfTriangles;
break;
case EStaticMeshReductionTerimationCriterion::Vertices:
OutSkeletalMeshReductionSettings.TerminationCriterion = SMTC_NumOfVerts;
break;
case EStaticMeshReductionTerimationCriterion::Any:
OutSkeletalMeshReductionSettings.TerminationCriterion = SMTC_TriangleOrVert;
break;
}
}
static bool AddLODFromMeshDescription(
FMeshDescription&& InMeshDescription,
USkeletalMesh* InSkeletalMesh,
IMeshUtilities& InMeshUtilities,
const bool bCacheOptimize = true)
{
FSkeletalMeshModel* ImportedModels = InSkeletalMesh->GetImportedModel();
const int32 LODIndex = ImportedModels->LODModels.Num();
ImportedModels->LODModels.Add(new FSkeletalMeshLODModel);
if (!ensure(ImportedModels->LODModels.Num() == InSkeletalMesh->GetLODNum()))
{
return false;
}
FSkeletalMeshImportData SkeletalMeshImportGeometry = FSkeletalMeshImportData::CreateFromMeshDescription(InMeshDescription);
InSkeletalMesh->CreateMeshDescription(LODIndex, MoveTemp(InMeshDescription));
InSkeletalMesh->CommitMeshDescription(LODIndex);
FSkeletalMeshLODModel& SkeletalMeshModel = ImportedModels->LODModels.Last();
// We need at least one set of texture coordinates. Always.
SkeletalMeshModel.NumTexCoords = FMath::Max<uint32>(1, SkeletalMeshImportGeometry.NumTexCoords);
// Data needed by BuildSkeletalMesh
TArray<FVector3f> LODPoints;
TArray<SkeletalMeshImportData::FMeshWedge> LODWedges;
TArray<SkeletalMeshImportData::FMeshFace> LODFaces;
TArray<SkeletalMeshImportData::FVertInfluence> LODInfluences;
TArray<int32> LODPointToRawMap;
SkeletalMeshImportGeometry.CopyLODImportData( LODPoints, LODWedges, LODFaces, LODInfluences, LODPointToRawMap );
IMeshUtilities::MeshBuildOptions BuildOptions;
BuildOptions.TargetPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform();
BuildOptions.FillOptions(InSkeletalMesh->GetLODInfo(InSkeletalMesh->GetLODNum() - 1)->BuildSettings);
BuildOptions.bCacheOptimize = bCacheOptimize;
TArray<FText> WarningMessages;
if (!InMeshUtilities.BuildSkeletalMesh(SkeletalMeshModel, InSkeletalMesh->GetPathName(), InSkeletalMesh->GetRefSkeleton(), LODInfluences, LODWedges, LODFaces, LODPoints, LODPointToRawMap, BuildOptions, &WarningMessages, nullptr))
{
for(const FText& Message: WarningMessages)
{
UE_LOG(LogStaticToSkeletalMeshConverter, Warning, TEXT("%s"), *Message.ToString());
}
return false;
}
return true;
}
static bool AddLODFromStaticMeshSourceModel(
const FStaticMeshSourceModel& InStaticMeshSourceModel,
USkeletalMesh* InSkeletalMesh,
const FBoneIndexType InBoneIndex,
IMeshUtilities& InMeshUtilities
)
{
// Always copy the build and reduction settings.
FSkeletalMeshLODInfo& SkeletalLODInfo = InSkeletalMesh->AddLODInfo();
SkeletalLODInfo.ScreenSize = InStaticMeshSourceModel.ScreenSize;
CopyBuildSettings(InStaticMeshSourceModel.BuildSettings, SkeletalLODInfo.BuildSettings);
CopyReductionSettings(InStaticMeshSourceModel.ReductionSettings, SkeletalLODInfo.ReductionSettings);
FSkeletalMeshModel* ImportedModels = InSkeletalMesh->GetImportedModel();
const int32 LODIndex = ImportedModels->LODModels.Num();
if (InStaticMeshSourceModel.IsMeshDescriptionValid())
{
FMeshDescription SkeletalMeshGeometry;
if (!InStaticMeshSourceModel.CloneMeshDescription(SkeletalMeshGeometry))
{
return false;
}
FSkeletalMeshAttributes SkeletalMeshAttributes(SkeletalMeshGeometry);
SkeletalMeshAttributes.Register();
// Fill Bones data.
const FReferenceSkeleton RefSkeleton = InSkeletalMesh->GetRefSkeleton();
const int32 NumRefBones = InSkeletalMesh->GetRefSkeleton().GetRawBoneNum();
FSkeletalMeshAttributes::FBoneArray& Bones = SkeletalMeshAttributes.Bones();
Bones.Reset(NumRefBones);
FSkeletalMeshAttributes::FBoneNameAttributesRef BoneNames = SkeletalMeshAttributes.GetBoneNames();
FSkeletalMeshAttributes::FBoneParentIndexAttributesRef BoneParentIndices = SkeletalMeshAttributes.GetBoneParentIndices();
FSkeletalMeshAttributes::FBonePoseAttributesRef BonePoses = SkeletalMeshAttributes.GetBonePoses();
for (int Index = 0; Index < NumRefBones; ++Index)
{
const FMeshBoneInfo& BoneInfo = RefSkeleton.GetRawRefBoneInfo()[Index];
const FTransform& BoneTransform = RefSkeleton.GetRawRefBonePose()[Index];
const FBoneID BoneID = SkeletalMeshAttributes.CreateBone();
BoneNames.Set(BoneID, BoneInfo.Name);
BoneParentIndices.Set(BoneID, BoneInfo.ParentIndex);
BonePoses.Set(BoneID, BoneTransform);
}
// Full binding to the root bone.
FSkinWeightsVertexAttributesRef SkinWeights = SkeletalMeshAttributes.GetVertexSkinWeights();
UE::AnimationCore::FBoneWeight RootInfluence(InBoneIndex, 1.0f);
UE::AnimationCore::FBoneWeights RootBinding = UE::AnimationCore::FBoneWeights::Create({RootInfluence});
for (const FVertexID VertexID: SkeletalMeshGeometry.Vertices().GetElementIDs())
{
SkinWeights.Set(VertexID, RootBinding);
}
// Convert weird static mesh inverse sRGB gamma to linear.
// FIXME: Remove once static mesh color space has been fixed to be linear again.
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = SkeletalMeshAttributes.GetVertexInstanceColors();
auto ConvertLinearToSRGBGamma = [](float V)
{
V = FMath::Clamp(V, 0.0f, 1.0f);
if (V <= 0.0031308)
{
return V * 12.92f;
}
else
{
return 1.055f * FMath::Pow(V, 1.0f / 2.4f) - 0.055f;
}
};
for (FVertexInstanceID VertexInstanceID: SkeletalMeshGeometry.VertexInstances().GetElementIDs())
{
FLinearColor VertexColor = VertexInstanceColors.Get(VertexInstanceID);
VertexColor.R = ConvertLinearToSRGBGamma(VertexColor.R);
VertexColor.G = ConvertLinearToSRGBGamma(VertexColor.G);
VertexColor.B = ConvertLinearToSRGBGamma(VertexColor.B);
VertexInstanceColors.Set(VertexInstanceID, VertexColor);
}
if (!AddLODFromMeshDescription(MoveTemp(SkeletalMeshGeometry), InSkeletalMesh, InMeshUtilities))
{
return false;
}
}
else
{
ImportedModels->LODModels.Add(new FSkeletalMeshLODModel);
FSkeletalMeshUpdateContext UpdateContext;
UpdateContext.SkeletalMesh = InSkeletalMesh;
FLODUtilities::SimplifySkeletalMeshLOD(UpdateContext, LODIndex, GetTargetPlatformManagerRef().GetRunningTargetPlatform());
}
return true;
}
static bool HasVertexColors(
const USkeletalMesh* InSkeletalMesh
)
{
for (const FSkeletalMeshLODModel& LODModel: InSkeletalMesh->GetImportedModel()->LODModels)
{
for (const FSkelMeshSection& Section: LODModel.Sections)
{
for (const FSoftSkinVertex& Vertex: Section.SoftVertices)
{
if (Vertex.Color != FColor::White)
{
return true;
}
}
}
}
return false;
}
bool FStaticToSkeletalMeshConverter::InitializeSkeletalMeshFromStaticMesh(
USkeletalMesh* InSkeletalMesh,
const UStaticMesh* InStaticMesh,
const FReferenceSkeleton& InReferenceSkeleton,
const FName InBindBone
)
{
if (!ensure(InSkeletalMesh))
{
return false;
}
if (!InSkeletalMesh->GetImportedModel()->LODModels.IsEmpty())
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Skeletal mesh '%s' is not empty"), *InSkeletalMesh->GetPathName());
return false;
}
if (!ensure(InStaticMesh))
{
return false;
}
int32 BoneIndex = 0;
if (!InBindBone.IsNone())
{
BoneIndex = InReferenceSkeleton.FindRawBoneIndex(InBindBone);
if (BoneIndex == INDEX_NONE)
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Bone '%s' not found in skeleton."), *InBindBone.ToString());
return false;
}
}
// This ensures that the render data gets built before we return, by calling PostEditChange when we fall out of scope.
FScopedSkeletalMeshPostEditChange ScopedPostEditChange( InSkeletalMesh );
InSkeletalMesh->PreEditChange( nullptr );
InSkeletalMesh->SetRefSkeleton(InReferenceSkeleton);
// Calculate the initial pose from the reference skeleton.
InSkeletalMesh->CalculateInvRefMatrices();
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>( "MeshUtilities" );
// Copy the LODs and LOD settings over (as close as we can).
bool bFirstSourceModel = true;
for (const FStaticMeshSourceModel& StaticMeshSourceModel: InStaticMesh->GetSourceModels())
{
if (!AddLODFromStaticMeshSourceModel(
StaticMeshSourceModel, InSkeletalMesh, static_cast<FBoneIndexType>(BoneIndex), MeshUtilities))
{
// If we didn't get a model for LOD index 0, we don't have a mesh. Bail out.
if (bFirstSourceModel)
{
return false;
}
// Otherwise, we have a model, so let's continue with what we have.
break;
}
bFirstSourceModel = false;
}
// Convert the materials over.
TArray<FSkeletalMaterial> Materials;
for (const FStaticMaterial& StaticMaterial: InStaticMesh->GetStaticMaterials())
{
FSkeletalMaterial Material(
StaticMaterial.MaterialInterface,
StaticMaterial.MaterialSlotName,
StaticMaterial.ImportedMaterialSlotName);
Materials.Add(Material);
}
InSkeletalMesh->SetMaterials(Materials);
if (HasVertexColors(InSkeletalMesh))
{
InSkeletalMesh->SetHasVertexColors(true);
InSkeletalMesh->SetVertexColorGuid(FGuid::NewGuid());
}
// Set the bounds from the static mesh, including the extensions, otherwise it won't render properly (among other things).
InSkeletalMesh->SetImportedBounds( InStaticMesh->GetBounds() );
InSkeletalMesh->SetPositiveBoundsExtension(InStaticMesh->GetPositiveBoundsExtension());
InSkeletalMesh->SetNegativeBoundsExtension(InStaticMesh->GetNegativeBoundsExtension());
//Create some import data so we can re-import this new skeletalmesh
UAssetImportData* OriginalAssetImportData = InStaticMesh->GetAssetImportData();
if (OriginalAssetImportData)
{
UAssetImportData* DuplicateAssetImportData = DuplicateObject<UAssetImportData>(OriginalAssetImportData, InSkeletalMesh);
DuplicateAssetImportData->ConvertAssetImportDataToNewOwner(InSkeletalMesh);
InSkeletalMesh->SetAssetImportData(DuplicateAssetImportData);
}
return true;
}
static bool ValidateSkinWeightAttribute(
const FMeshDescription& InMeshDescription,
const FReferenceSkeleton& InReferenceSkeleton
)
{
using namespace UE::AnimationCore;
FSkeletalMeshConstAttributes MeshAttributes{InMeshDescription};
TArray<FName> Profiles = MeshAttributes.GetSkinWeightProfileNames();
if (Profiles.IsEmpty())
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Mesh description doesn't have a skin weight attribute."));
return false;
}
FBoneIndexType BoneIndexMax = static_cast<FBoneIndexType>(InReferenceSkeleton.GetRawBoneNum());
// We use the first profile. Usually that's the default profile, unless we have nothing but alternate profiles.
FSkinWeightsVertexAttributesConstRef VertexSkinWeights = MeshAttributes.GetVertexSkinWeights(Profiles[0]);
for (const FVertexID VertexID: InMeshDescription.Vertices().GetElementIDs())
{
for (FBoneWeight BoneWeight: VertexSkinWeights.Get(VertexID))
{
if (BoneWeight.GetBoneIndex() >= BoneIndexMax)
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Mesh description's skin weight refers to a non-existent bone (%d of %d)."), BoneWeight.GetBoneIndex(), BoneIndexMax);
return false;
}
}
}
return true;
}
bool FStaticToSkeletalMeshConverter::InitializeSkeletalMeshFromMeshDescriptions(
USkeletalMesh* InSkeletalMesh,
TArrayView<const FMeshDescription*> InMeshDescriptions,
TConstArrayView<FSkeletalMaterial> InMaterials,
const FReferenceSkeleton& InReferenceSkeleton,
const bool bInRecomputeNormals,
const bool bInRecomputeTangents,
const bool bCacheOptimize
)
{
if (!ensure(InSkeletalMesh))
{
return false;
}
if (!InSkeletalMesh->GetImportedModel()->LODModels.IsEmpty())
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("Skeletal mesh '%s' is not empty"), *InSkeletalMesh->GetPathName());
return false;
}
if (InMeshDescriptions.IsEmpty())
{
UE_LOG(LogStaticToSkeletalMeshConverter, Error, TEXT("No mesh descriptions given"));
return false;
}
// Ensure all mesh descriptions have a skin weight attribute.
for (const FMeshDescription* MeshDescription: InMeshDescriptions)
{
if (!ValidateSkinWeightAttribute(*MeshDescription, InReferenceSkeleton))
{
return false;
}
}
// Set the materials before we start converting. We'll add dummy materials afterward if there are more sections
// than materials in any of the LODs. Not the best system, but the best we have for now.
InSkeletalMesh->SetMaterials(TArray<FSkeletalMaterial>{InMaterials});
TSet<FName> ValidMaterialSlotNames;
for (int32 Index = 0; Index < InMaterials.Num(); Index++)
{
const FSkeletalMaterial& Material = InMaterials[Index];
if (!Material.MaterialSlotName.IsNone())
{
ValidMaterialSlotNames.Add(Material.MaterialSlotName);
}
}
// This ensures that the render data gets built before we return, by calling PostEditChange when we fall out of scope.
{
FScopedSkeletalMeshPostEditChange ScopedPostEditChange( InSkeletalMesh );
InSkeletalMesh->PreEditChange( nullptr );
InSkeletalMesh->SetRefSkeleton(InReferenceSkeleton);
// Calculate the initial pose from the reference skeleton.
InSkeletalMesh->CalculateInvRefMatrices();
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>( "MeshUtilities" );
bool bFirstSourceModel = true;
for (const FMeshDescription* MeshDescription: InMeshDescriptions)
{
// Add default LOD build settings.
FSkeletalMeshLODInfo& SkeletalLODInfo = InSkeletalMesh->AddLODInfo();
SkeletalLODInfo.ReductionSettings.NumOfTrianglesPercentage = 1.0f;
SkeletalLODInfo.ReductionSettings.NumOfVertPercentage = 1.0f;
SkeletalLODInfo.ReductionSettings.MaxDeviationPercentage = 0.0f;
SkeletalLODInfo.LODHysteresis = 0.02f;
SkeletalLODInfo.BuildSettings.bRecomputeNormals = bInRecomputeNormals;
SkeletalLODInfo.BuildSettings.bRecomputeTangents = bInRecomputeTangents;
FMeshDescription ClonedDescription(*MeshDescription);
// Fix up the material slot names on the mesh to match the ones in the material list. If the name is
// either NAME_None, or doesn't exist in the material list, we use the group index to index into the
// material list to resolve the name.
FSkeletalMeshAttributes Attributes(ClonedDescription);
TPolygonGroupAttributesRef<FName> MaterialSlotNamesAttribute = Attributes.GetPolygonGroupMaterialSlotNames();
for (FPolygonGroupID PolygonGroupID: ClonedDescription.PolygonGroups().GetElementIDs())
{
if (!ValidMaterialSlotNames.Contains(MaterialSlotNamesAttribute.Get(PolygonGroupID)))
{
int32 MaterialIndex = PolygonGroupID.GetValue();
MaterialIndex = FMath::Clamp(MaterialIndex, 0, InMaterials.Num() - 1);
MaterialSlotNamesAttribute.Set(PolygonGroupID, InMaterials[MaterialIndex].MaterialSlotName);
}
}
if (!AddLODFromMeshDescription(MoveTemp(ClonedDescription), InSkeletalMesh, MeshUtilities, bCacheOptimize))
{
// If we didn't get a model for LOD index 0, we don't have a mesh. Bail out.
if (bFirstSourceModel)
{
return false;
}
// Otherwise, we have a model, so let's continue with what we have.
break;
}
bFirstSourceModel = false;
}
}
// Compute the bbox, now that we have the model mesh generated.
FBox3f BoundingBox{ForceInit};
int32 MaxSectionCount = 0;
for (const FSkeletalMeshLODModel& MeshModel: InSkeletalMesh->GetImportedModel()->LODModels)
{
MaxSectionCount = FMath::Max(MaxSectionCount, MeshModel.Sections.Num());
// Compute the overall bbox.
for (const FSkelMeshSection& Section: MeshModel.Sections)
{
for (const FSoftSkinVertex& Vertex: Section.SoftVertices)
{
BoundingBox += Vertex.Position;
}
}
}
// If we're short on materials, compared to sections, add dummy materials to fill in the gap. Not ideal, but
// best we can do for now.
const TArray<FSkeletalMaterial>& ExistingMaterials = InSkeletalMesh->GetMaterials();
if (MaxSectionCount > ExistingMaterials.Num())
{
TArray<FSkeletalMaterial> NewMaterials{ExistingMaterials};
for (int32 Index = ExistingMaterials.Num(); Index < MaxSectionCount; Index++)
{
NewMaterials.Add(FSkeletalMaterial{});
}
InSkeletalMesh->SetMaterials(NewMaterials);
}
InSkeletalMesh->SetImportedBounds( FBox3d{BoundingBox} );
return true;
}
#endif