EM_Task/UnrealEd/Private/ImportUtils/SkeletalMeshImportUtils.cpp

1135 lines
59 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
SkeletalMeshImportUtils.cpp: Skeletal mesh import code.
=============================================================================*/
#include "ImportUtils/SkeletalMeshImportUtils.h"
#include "ClothingAssetBase.h"
#include "CoreMinimal.h"
#include "EditorFramework/ThumbnailInfo.h"
#include "Engine/AssetUserData.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Factories/FbxSkeletalMeshImportData.h"
#include "FbxImporter.h"
#include "ImportUtils/InternalImportUtils.h"
#include "ImportUtils/SkelImport.h"
#include "Interfaces/ITargetPlatform.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "LODUtilities.h"
#include "Materials/MaterialInterface.h"
#include "Misc/CoreMisc.h"
#include "Misc/FbxErrors.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "ReferenceSkeleton.h"
#include "Rendering/SkeletalMeshLODImporterData.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "Rendering/SkeletalMeshModel.h"
#include "UObject/MetaData.h"
#include "UObject/UObjectIterator.h"
DEFINE_LOG_CATEGORY_STATIC(LogSkeletalMeshImport, Log, All);
#define LOCTEXT_NAMESPACE "SkeletalMeshImport"
namespace SkeletalMesUtilsImpl
{
/** Check that root bone is the same, and that any bones that are common have the correct parent. */
bool SkeletonsAreCompatible(const FReferenceSkeleton& NewSkel, const FReferenceSkeleton& ExistSkel, bool bFailNoError);
void SaveSkeletalMeshLODModelSections(USkeletalMesh* SourceSkeletalMesh, TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, int32 LodIndex, bool bSaveNonReducedMeshData);
void SaveSkeletalMeshMaterialNameWorkflowData(TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, const USkeletalMesh* SourceSkeletalMesh);
void SaveSkeletalMeshAssetUserData(TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, const TArray<UAssetUserData*>* UserData);
void RestoreDependentLODs(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh);
void RestoreLODInfo(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh, int32 LodIndex);
void RestoreMaterialNameWorkflowSection(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh, int32 LodIndex, TArray<int32>& RemapMaterial, bool bMaterialReset);
} // namespace SkeletalMesUtilsImpl
bool SkeletalMesUtilsImpl::SkeletonsAreCompatible(const FReferenceSkeleton& NewSkel, const FReferenceSkeleton& ExistSkel, bool bFailNoError)
{
if (NewSkel.GetBoneName(0) != ExistSkel.GetBoneName(0))
{
if (!bFailNoError)
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("MeshHasDifferentRoot", "Root Bone is '{0}' instead of '{1}'.\nDiscarding existing LODs."),
FText::FromName(NewSkel.GetBoneName(0)), FText::FromName(ExistSkel.GetBoneName(0)))),
FFbxErrors::SkeletalMesh_DifferentRoots);
}
return false;
}
for (int32 i = 1; i < NewSkel.GetRawBoneNum(); i++)
{
// See if bone is in both skeletons.
int32 NewBoneIndex = i;
FName NewBoneName = NewSkel.GetBoneName(NewBoneIndex);
int32 BBoneIndex = ExistSkel.FindBoneIndex(NewBoneName);
// If it is, check parents are the same.
if (BBoneIndex != INDEX_NONE)
{
FName NewParentName = NewSkel.GetBoneName(NewSkel.GetParentIndex(NewBoneIndex));
FName ExistParentName = ExistSkel.GetBoneName(ExistSkel.GetParentIndex(BBoneIndex));
if (NewParentName != ExistParentName)
{
if (!bFailNoError)
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("MeshHasDifferentRoot", "Root Bone is '{0}' instead of '{1}'.\nDiscarding existing LODs."),
FText::FromName(NewBoneName), FText::FromName(NewParentName))),
FFbxErrors::SkeletalMesh_DifferentRoots);
}
return false;
}
}
}
return true;
}
/**
* Process and fill in the mesh Materials using the raw binary import data
*
* @param Materials - [out] array of materials to update
* @param ImportData - raw binary import data to process
*/
void SkeletalMeshImportUtils::ProcessImportMeshMaterials(TArray<FSkeletalMaterial>& Materials, FSkeletalMeshImportData& ImportData)
{
TArray<SkeletalMeshImportData::FMaterial>& ImportedMaterials = ImportData.Materials;
// If direct linkup of materials is requested, try to find them here - to get a texture name from a
// material name, cut off anything in front of the dot (beyond are special flags).
Materials.Empty();
int32 SkinOffset = INDEX_NONE;
for (int32 MatIndex = 0; MatIndex < ImportedMaterials.Num(); ++MatIndex)
{
const SkeletalMeshImportData::FMaterial& ImportedMaterial = ImportedMaterials[MatIndex];
UMaterialInterface* Material = NULL;
FString MaterialNameNoSkin = ImportedMaterial.MaterialImportName;
if (ImportedMaterial.Material.IsValid())
{
Material = ImportedMaterial.Material.Get();
}
else
{
const FString& MaterialName = ImportedMaterial.MaterialImportName;
MaterialNameNoSkin = MaterialName;
Material = FindObject<UMaterialInterface>(ANY_PACKAGE, *MaterialName);
if (Material == nullptr)
{
SkinOffset = MaterialName.Find(TEXT("_skin"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
if (SkinOffset != INDEX_NONE)
{
FString SkinXXNumber = MaterialName.Right(MaterialName.Len() - (SkinOffset + 1)).RightChop(4);
if (SkinXXNumber.IsNumeric())
{
MaterialNameNoSkin = MaterialName.LeftChop(MaterialName.Len() - SkinOffset);
Material = FindObject<UMaterialInterface>(ANY_PACKAGE, *MaterialNameNoSkin);
}
}
}
}
const bool bEnableShadowCasting = true;
Materials.Add(FSkeletalMaterial(Material, bEnableShadowCasting, false, Material != nullptr ? Material->GetFName() : FName(*MaterialNameNoSkin), FName(*(ImportedMaterial.MaterialImportName))));
}
int32 NumMaterialsToAdd = FMath::Max<int32>(ImportedMaterials.Num(), ImportData.MaxMaterialIndex + 1);
// Pad the material pointers
while (NumMaterialsToAdd > Materials.Num())
{
Materials.Add(FSkeletalMaterial(NULL, true, false, NAME_None, NAME_None));
}
}
/**
* Process and fill in the mesh ref skeleton bone hierarchy using the raw binary import data
*
* @param RefSkeleton - [out] reference skeleton hierarchy to update
* @param SkeletalDepth - [out] depth of the reference skeleton hierarchy
* @param ImportData - raw binary import data to process
* @return true if the operation completed successfully
*/
bool SkeletalMeshImportUtils::ProcessImportMeshSkeleton(const USkeleton* SkeletonAsset, FReferenceSkeleton& RefSkeleton, int32& SkeletalDepth, FSkeletalMeshImportData& ImportData)
{
TArray<SkeletalMeshImportData::FBone>& RefBonesBinary = ImportData.RefBonesBinary;
// Setup skeletal hierarchy + names structure.
RefSkeleton.Empty();
FReferenceSkeletonModifier RefSkelModifier(RefSkeleton, SkeletonAsset);
// Digest bones to the serializable format.
for (int32 b = 0; b < RefBonesBinary.Num(); b++)
{
const SkeletalMeshImportData::FBone& BinaryBone = RefBonesBinary[b];
const FString BoneName = FSkeletalMeshImportData::FixupBoneName(BinaryBone.Name);
const FMeshBoneInfo BoneInfo(FName(*BoneName, FNAME_Add), BinaryBone.Name, BinaryBone.ParentIndex);
const FTransform BoneTransform(BinaryBone.BonePos.Transform);
if (RefSkeleton.FindRawBoneIndex(BoneInfo.Name) != INDEX_NONE)
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("SkeletonHasDuplicateBones", "Skeleton has non-unique bone names.\nBone named '{0}' encountered more than once."), FText::FromName(BoneInfo.Name))), FFbxErrors::SkeletalMesh_DuplicateBones);
return false;
}
RefSkelModifier.Add(BoneInfo, BoneTransform);
}
// Add hierarchy index to each bone and detect max depth.
SkeletalDepth = 0;
TArray<int32> SkeletalDepths;
SkeletalDepths.Empty(RefBonesBinary.Num());
SkeletalDepths.AddZeroed(RefBonesBinary.Num());
for (int32 b = 0; b < RefSkeleton.GetRawBoneNum(); b++)
{
int32 Parent = RefSkeleton.GetRawParentIndex(b);
int32 Depth = 1.0f;
SkeletalDepths[b] = 1.0f;
if (Parent != INDEX_NONE)
{
Depth += SkeletalDepths[Parent];
}
if (SkeletalDepth < Depth)
{
SkeletalDepth = Depth;
}
SkeletalDepths[b] = Depth;
}
return true;
}
/**
* Process and update the vertex Influences using the raw binary import data
*
* @param ImportData - raw binary import data to process
*/
void SkeletalMeshImportUtils::ProcessImportMeshInfluences(FSkeletalMeshImportData& ImportData, const FString& SkeletalMeshName)
{
FLODUtilities::ProcessImportMeshInfluences(ImportData.Wedges.Num(), ImportData.Influences, SkeletalMeshName);
}
void SkeletalMesUtilsImpl::SaveSkeletalMeshLODModelSections(USkeletalMesh* SourceSkeletalMesh, TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, int32 LodIndex, bool bSaveNonReducedMeshData)
{
const FSkeletalMeshModel* SourceMeshModel = SourceSkeletalMesh->GetImportedModel();
const FSkeletalMeshLODModel* SourceLODModel = &SourceMeshModel->LODModels[LodIndex];
FSkeletalMeshLODModel OriginalLODModel;
if (bSaveNonReducedMeshData && (SourceMeshModel->OriginalReductionSourceMeshData.IsValidIndex(LodIndex) && !SourceMeshModel->OriginalReductionSourceMeshData[LodIndex]->IsEmpty()))
{
TMap<FString, TArray<FMorphTargetDelta>> TempLODMorphTargetData;
// Get the before reduce LODModel, this lod model contain all the possible sections
SourceMeshModel->OriginalReductionSourceMeshData[LodIndex]->LoadReductionData(OriginalLODModel, TempLODMorphTargetData, SourceSkeletalMesh);
// If there was section that was remove by the reduction (Disabled in the original data, zero triangle after reduction, GenerateUpTo settings...),
// we have to use the original section data and apply the section data that was modified after the reduction
if (OriginalLODModel.Sections.Num() > SourceLODModel->Sections.Num())
{
TArray<bool> OriginalMatched;
OriginalMatched.AddZeroed(OriginalLODModel.Sections.Num());
// Now apply the after reduce settings change, but we need to match the section since there can be reduced one
for (int32 ReduceSectionIndex = 0; ReduceSectionIndex < SourceLODModel->Sections.Num(); ++ReduceSectionIndex)
{
const FSkelMeshSection& ReduceSection = SourceLODModel->Sections[ReduceSectionIndex];
for (int32 OriginalSectionIndex = 0; OriginalSectionIndex < OriginalLODModel.Sections.Num(); ++OriginalSectionIndex)
{
if (OriginalMatched[OriginalSectionIndex])
{
continue;
}
FSkelMeshSection& OriginalSection = OriginalLODModel.Sections[OriginalSectionIndex];
if ((OriginalSection.bDisabled) || (OriginalSection.GenerateUpToLodIndex != INDEX_NONE && OriginalSection.GenerateUpToLodIndex < LodIndex))
{
continue;
}
if (ReduceSection.MaterialIndex == OriginalSection.MaterialIndex)
{
OriginalMatched[OriginalSectionIndex] = true;
OriginalSection.bDisabled = ReduceSection.bDisabled;
OriginalSection.bCastShadow = ReduceSection.bCastShadow;
OriginalSection.bRecomputeTangent = ReduceSection.bRecomputeTangent;
OriginalSection.RecomputeTangentsVertexMaskChannel = ReduceSection.RecomputeTangentsVertexMaskChannel;
OriginalSection.GenerateUpToLodIndex = ReduceSection.GenerateUpToLodIndex;
break;
}
}
}
// Set the unmatched original section data using the current UserSectionsData so we keep the user changes
for (int32 OriginalSectionIndex = 0; OriginalSectionIndex < OriginalLODModel.Sections.Num(); ++OriginalSectionIndex)
{
if (OriginalMatched[OriginalSectionIndex])
{
continue;
}
FSkelMeshSection& OriginalSection = OriginalLODModel.Sections[OriginalSectionIndex];
if (const FSkelMeshSourceSectionUserData* ReduceUserSectionData = SourceLODModel->UserSectionsData.Find(OriginalSection.OriginalDataSectionIndex))
{
OriginalSection.bDisabled = ReduceUserSectionData->bDisabled;
OriginalSection.bCastShadow = ReduceUserSectionData->bCastShadow;
OriginalSection.bRecomputeTangent = ReduceUserSectionData->bRecomputeTangent;
OriginalSection.RecomputeTangentsVertexMaskChannel = ReduceUserSectionData->RecomputeTangentsVertexMaskChannel;
OriginalSection.GenerateUpToLodIndex = ReduceUserSectionData->GenerateUpToLodIndex;
}
}
// Use the OriginalLODModel
SourceLODModel = &OriginalLODModel;
}
}
ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.AddZeroed();
check(ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData.IsValidIndex(LodIndex));
for (const FSkelMeshSection& CurrentSection: SourceLODModel->Sections)
{
int32 SectionMaterialIndex = CurrentSection.MaterialIndex;
bool SectionCastShadow = CurrentSection.bCastShadow;
bool SectionRecomputeTangents = CurrentSection.bRecomputeTangent;
ESkinVertexColorChannel RecomputeTangentsVertexMaskChannel = CurrentSection.RecomputeTangentsVertexMaskChannel;
int32 GenerateUpTo = CurrentSection.GenerateUpToLodIndex;
bool bDisabled = CurrentSection.bDisabled;
bool bBoneChunkedSection = CurrentSection.ChunkedParentSectionIndex != INDEX_NONE;
// Save all the sections, even the chunked sections
if (ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.IsValidIndex(SectionMaterialIndex))
{
ExistingMeshDataPtr->ExistingImportMeshLodSectionMaterialData[LodIndex].Emplace(ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData[SectionMaterialIndex], SectionCastShadow, SectionRecomputeTangents, RecomputeTangentsVertexMaskChannel, GenerateUpTo, bDisabled);
}
}
}
void SkeletalMesUtilsImpl::SaveSkeletalMeshMaterialNameWorkflowData(TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, const USkeletalMesh* SourceSkeletalMesh)
{
const UFbxSkeletalMeshImportData* ImportData = Cast<UFbxSkeletalMeshImportData>(SourceSkeletalMesh->GetAssetImportData());
if (!ImportData)
{
return;
}
for (int32 ImportMaterialOriginalNameDataIndex = 0; ImportMaterialOriginalNameDataIndex < ImportData->ImportMaterialOriginalNameData.Num(); ++ImportMaterialOriginalNameDataIndex)
{
FName MaterialName = ImportData->ImportMaterialOriginalNameData[ImportMaterialOriginalNameDataIndex];
ExistingMeshDataPtr->LastImportMaterialOriginalNameData.Add(MaterialName);
}
for (int32 LodIndex = 0; LodIndex < ImportData->ImportMeshLodData.Num(); ++LodIndex)
{
ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData.AddZeroed();
const FImportMeshLodSectionsData& ImportMeshLodSectionsData = ImportData->ImportMeshLodData[LodIndex];
for (int32 SectionIndex = 0; SectionIndex < ImportMeshLodSectionsData.SectionOriginalMaterialName.Num(); ++SectionIndex)
{
FName MaterialName = ImportMeshLodSectionsData.SectionOriginalMaterialName[SectionIndex];
ExistingMeshDataPtr->LastImportMeshLodSectionMaterialData[LodIndex].Add(MaterialName);
}
}
}
void SkeletalMesUtilsImpl::SaveSkeletalMeshAssetUserData(TSharedPtr<FExistingSkelMeshData>& ExistingMeshDataPtr, const TArray<UAssetUserData*>* UserData)
{
if (!UserData)
{
return;
}
for (int32 Idx = 0; Idx < UserData->Num(); Idx++)
{
if ((*UserData)[Idx] != nullptr)
{
UAssetUserData* DupObject = (UAssetUserData*)StaticDuplicateObject((*UserData)[Idx], GetTransientPackage());
bool bAddDupToRoot = !(DupObject->IsRooted());
if (bAddDupToRoot)
{
DupObject->AddToRoot();
}
ExistingMeshDataPtr->ExistingAssetUserData.Add(DupObject, bAddDupToRoot);
}
}
}
TSharedPtr<FExistingSkelMeshData> SkeletalMeshImportUtils::SaveExistingSkelMeshData(USkeletalMesh* SourceSkeletalMesh, bool bSaveMaterials, int32 ReimportLODIndex)
{
using namespace SkeletalMesUtilsImpl;
if (!SourceSkeletalMesh)
{
return TSharedPtr<FExistingSkelMeshData>();
}
const int32 SafeReimportLODIndex = ReimportLODIndex < 0 ? 0 : ReimportLODIndex;
TSharedPtr<FExistingSkelMeshData> ExistingMeshDataPtr(MakeShared<FExistingSkelMeshData>());
// Save the package UMetaData
TMap<FName, FString>* MetaDataTagValues = UMetaData::GetMapForObject(SourceSkeletalMesh);
if (MetaDataTagValues && MetaDataTagValues->Num() > 0)
{
ExistingMeshDataPtr->ExistingUMetaDataTagValues = *MetaDataTagValues;
}
ExistingMeshDataPtr->UseMaterialNameSlotWorkflow = InternalImportUtils::IsUsingMaterialSlotNameWorkflow(SourceSkeletalMesh->GetAssetImportData());
ExistingMeshDataPtr->MinLOD = SourceSkeletalMesh->GetMinLod();
ExistingMeshDataPtr->DisableBelowMinLodStripping = SourceSkeletalMesh->GetDisableBelowMinLodStripping();
ExistingMeshDataPtr->bOverrideLODStreamingSettings = SourceSkeletalMesh->GetOverrideLODStreamingSettings();
ExistingMeshDataPtr->bSupportLODStreaming = SourceSkeletalMesh->GetSupportLODStreaming();
ExistingMeshDataPtr->MaxNumStreamedLODs = SourceSkeletalMesh->GetMaxNumStreamedLODs();
ExistingMeshDataPtr->MaxNumOptionalLODs = SourceSkeletalMesh->GetMaxNumOptionalLODs();
const TArray<FSkeletalMaterial>& SourceSkeletalMeshMaterials = SourceSkeletalMesh->GetMaterials();
// Add the existing Material slot name data
for (int32 MaterialIndex = 0; MaterialIndex < SourceSkeletalMeshMaterials.Num(); ++MaterialIndex)
{
ExistingMeshDataPtr->ExistingImportMaterialOriginalNameData.Add(SourceSkeletalMeshMaterials[MaterialIndex].ImportedMaterialSlotName);
}
FSkeletalMeshModel* SourceMeshModel = SourceSkeletalMesh->GetImportedModel();
for (int32 LodIndex = 0; LodIndex < SourceMeshModel->LODModels.Num(); ++LodIndex)
{
const bool bImportNonReducedData = LodIndex == SafeReimportLODIndex;
SaveSkeletalMeshLODModelSections(SourceSkeletalMesh, ExistingMeshDataPtr, LodIndex, bImportNonReducedData);
}
ExistingMeshDataPtr->ExistingSockets = SourceSkeletalMesh->GetMeshOnlySocketList();
ExistingMeshDataPtr->bSaveRestoreMaterials = bSaveMaterials;
if (ExistingMeshDataPtr->bSaveRestoreMaterials)
{
ExistingMeshDataPtr->ExistingMaterials = SourceSkeletalMesh->GetMaterials();
}
ExistingMeshDataPtr->ExistingRetargetBasePose = SourceSkeletalMesh->GetRetargetBasePose();
if (SourceMeshModel->LODModels.Num() > 0 &&
SourceSkeletalMesh->GetLODNum() == SourceMeshModel->LODModels.Num())
{
// Copy LOD models and LOD Infos.
check(SourceMeshModel->LODModels.Num() == SourceSkeletalMesh->GetLODInfoArray().Num());
ExistingMeshDataPtr->ExistingLODModels.Empty(SourceMeshModel->LODModels.Num());
for (int32 LODIndex = 0; LODIndex < SourceMeshModel->LODModels.Num(); ++LODIndex)
{
// const int32 ReductionLODIndex = LODModelIndex + OffsetReductionLODIndex;
TSharedPtr<FReductionBaseSkeletalMeshBulkData> ReductionLODData;
if (SourceMeshModel->OriginalReductionSourceMeshData.IsValidIndex(LODIndex) && !SourceMeshModel->OriginalReductionSourceMeshData[LODIndex]->IsEmpty())
{
FSkeletalMeshLODModel BaseLODModel;
TMap<FString, TArray<FMorphTargetDelta>> BaseLODMorphTargetData;
SourceMeshModel->OriginalReductionSourceMeshData[LODIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData, SourceSkeletalMesh);
ReductionLODData = MakeShared<FReductionBaseSkeletalMeshBulkData>();
ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData, SourceSkeletalMesh);
}
// Add the reduction source mesh data if it exist, otherwise an empty sharedPtr.
ExistingMeshDataPtr->ExistingOriginalReductionSourceMeshData.Add(MoveTemp(ReductionLODData));
// Add a new LOD Model to the existing LODModels data
const FSkeletalMeshLODModel& LODModel = SourceMeshModel->LODModels[LODIndex];
ExistingMeshDataPtr->ExistingLODModels.Add(FSkeletalMeshLODModel::CreateCopy(&LODModel));
}
check(ExistingMeshDataPtr->ExistingLODModels.Num() == SourceMeshModel->LODModels.Num());
ExistingMeshDataPtr->ExistingLODInfo = SourceSkeletalMesh->GetLODInfoArray();
ExistingMeshDataPtr->ExistingRefSkeleton = SourceSkeletalMesh->GetRefSkeleton();
}
// First asset should be the one that the skeletal mesh should point too
ExistingMeshDataPtr->ExistingPhysicsAssets.Empty();
ExistingMeshDataPtr->ExistingPhysicsAssets.Add(SourceSkeletalMesh->GetPhysicsAsset());
for (TObjectIterator<UPhysicsAsset> It; It; ++It)
{
UPhysicsAsset* PhysicsAsset = *It;
if (PhysicsAsset->PreviewSkeletalMesh == SourceSkeletalMesh && SourceSkeletalMesh->GetPhysicsAsset() != PhysicsAsset)
{
ExistingMeshDataPtr->ExistingPhysicsAssets.Add(PhysicsAsset);
}
}
ExistingMeshDataPtr->ExistingShadowPhysicsAsset = SourceSkeletalMesh->GetShadowPhysicsAsset();
ExistingMeshDataPtr->ExistingSkeleton = SourceSkeletalMesh->GetSkeleton();
// since copying back original skeleton, this should be safe to do
ExistingMeshDataPtr->ExistingPostProcessAnimBlueprint = SourceSkeletalMesh->GetPostProcessAnimBlueprint();
ExistingMeshDataPtr->ExistingLODSettings = SourceSkeletalMesh->GetLODSettings();
SourceSkeletalMesh->ExportMirrorTable(ExistingMeshDataPtr->ExistingMirrorTable);
ExistingMeshDataPtr->ExistingMorphTargets = SourceSkeletalMesh->GetMorphTargets();
ExistingMeshDataPtr->ExistingAssetImportData = SourceSkeletalMesh->GetAssetImportData();
ExistingMeshDataPtr->ExistingThumbnailInfo = SourceSkeletalMesh->GetThumbnailInfo();
ExistingMeshDataPtr->ExistingClothingAssets = SourceSkeletalMesh->GetMeshClothingAssets();
ExistingMeshDataPtr->ExistingSamplingInfo = SourceSkeletalMesh->GetSamplingInfo();
ExistingMeshDataPtr->ExistingDefaultAnimatingRig = SourceSkeletalMesh->GetDefaultAnimatingRig();
if (ExistingMeshDataPtr->UseMaterialNameSlotWorkflow)
{
// Add the last fbx import data
SaveSkeletalMeshMaterialNameWorkflowData(ExistingMeshDataPtr, SourceSkeletalMesh);
}
// Store the user asset data
SaveSkeletalMeshAssetUserData(ExistingMeshDataPtr, SourceSkeletalMesh->GetAssetUserDataArray());
// Store mesh changed delegate data
ExistingMeshDataPtr->ExistingOnMeshChanged = SourceSkeletalMesh->GetOnMeshChanged();
// Store mesh bounds extensions
ExistingMeshDataPtr->PositiveBoundsExtension = SourceSkeletalMesh->GetPositiveBoundsExtension();
ExistingMeshDataPtr->NegativeBoundsExtension = SourceSkeletalMesh->GetNegativeBoundsExtension();
return ExistingMeshDataPtr;
}
void SkeletalMesUtilsImpl::RestoreDependentLODs(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh)
{
check(SkeletalMesh != nullptr);
const int32 TotalLOD = MeshData->ExistingLODModels.Num();
FSkeletalMeshModel* SkeletalMeshImportedModel = SkeletalMesh->GetImportedModel();
for (int32 LODIndex = 1; LODIndex < TotalLOD; ++LODIndex)
{
if (LODIndex >= SkeletalMesh->GetLODInfoArray().Num())
{
// Create a copy of LODInfo and reset material maps, it won't work anyway.
FSkeletalMeshLODInfo ExistLODInfo = MeshData->ExistingLODInfo[LODIndex];
ExistLODInfo.LODMaterialMap.Empty();
// add LOD info back
SkeletalMesh->AddLODInfo(MoveTemp(ExistLODInfo));
check(LODIndex < SkeletalMesh->GetLODInfoArray().Num());
const FSkeletalMeshLODModel& ExistLODModel = MeshData->ExistingLODModels[LODIndex];
SkeletalMeshImportedModel->LODModels.Add(FSkeletalMeshLODModel::CreateCopy(&ExistLODModel));
}
}
}
void SkeletalMesUtilsImpl::RestoreLODInfo(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh, int32 LodIndex)
{
FSkeletalMeshLODInfo& ImportedLODInfo = SkeletalMesh->GetLODInfoArray()[LodIndex];
if (!MeshData->ExistingLODInfo.IsValidIndex(LodIndex))
{
return;
}
const FSkeletalMeshLODInfo& ExistingLODInfo = MeshData->ExistingLODInfo[LodIndex];
ImportedLODInfo.ScreenSize = ExistingLODInfo.ScreenSize;
ImportedLODInfo.LODHysteresis = ExistingLODInfo.LODHysteresis;
ImportedLODInfo.BuildSettings = ExistingLODInfo.BuildSettings;
// Old assets may have non-applied reduction settings, so only restore the reduction settings if the LOD was effectively reduced.
if (ExistingLODInfo.bHasBeenSimplified)
{
ImportedLODInfo.ReductionSettings = ExistingLODInfo.ReductionSettings;
}
ImportedLODInfo.BonesToRemove = ExistingLODInfo.BonesToRemove;
ImportedLODInfo.BonesToPrioritize = ExistingLODInfo.BonesToPrioritize;
ImportedLODInfo.WeightOfPrioritization = ExistingLODInfo.WeightOfPrioritization;
ImportedLODInfo.BakePose = ExistingLODInfo.BakePose;
ImportedLODInfo.BakePoseOverride = ExistingLODInfo.BakePoseOverride;
ImportedLODInfo.SourceImportFilename = ExistingLODInfo.SourceImportFilename;
ImportedLODInfo.SkinCacheUsage = ExistingLODInfo.SkinCacheUsage;
ImportedLODInfo.bAllowCPUAccess = ExistingLODInfo.bAllowCPUAccess;
ImportedLODInfo.bSupportUniformlyDistributedSampling = ExistingLODInfo.bSupportUniformlyDistributedSampling;
}
void SkeletalMeshImportUtils::ApplySkinning(USkeletalMesh* SkeletalMesh, FSkeletalMeshLODModel& SrcLODModel, FSkeletalMeshLODModel& DestLODModel)
{
TArray<FSoftSkinVertex> SrcVertices;
SrcLODModel.GetVertices(SrcVertices);
FBox OldBounds(EForceInit::ForceInit);
for (int32 SrcIndex = 0; SrcIndex < SrcVertices.Num(); ++SrcIndex)
{
const FSoftSkinVertex& SrcVertex = SrcVertices[SrcIndex];
OldBounds += SrcVertex.Position;
}
TWedgeInfoPosOctree SrcWedgePosOctree(OldBounds.GetCenter(), OldBounds.GetExtent().GetMax());
// Add each old vertex to the octree
for (int32 SrcIndex = 0; SrcIndex < SrcVertices.Num(); ++SrcIndex)
{
FWedgeInfo WedgeInfo;
WedgeInfo.WedgeIndex = SrcIndex;
WedgeInfo.Position = SrcVertices[SrcIndex].Position;
SrcWedgePosOctree.AddElement(WedgeInfo);
}
FOctreeQueryHelper OctreeQueryHelper(&SrcWedgePosOctree);
TArray<FBoneIndexType> RequiredActiveBones;
bool bUseBone = false;
for (int32 SectionIndex = 0; SectionIndex < DestLODModel.Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = DestLODModel.Sections[SectionIndex];
Section.BoneMap.Reset();
for (FSoftSkinVertex& DestVertex: Section.SoftVertices)
{
// Find the nearest wedges in the src model
TArray<FWedgeInfo> NearestSrcWedges;
OctreeQueryHelper.FindNearestWedgeIndexes(DestVertex.Position, NearestSrcWedges);
if (NearestSrcWedges.Num() < 1)
{
// Should we check???
continue;
}
// Find the matching wedges in the src model
int32 MatchingSrcWedge = INDEX_NONE;
for (FWedgeInfo& SrcWedgeInfo: NearestSrcWedges)
{
int32 SrcIndex = SrcWedgeInfo.WedgeIndex;
const FSoftSkinVertex& SrcVertex = SrcVertices[SrcIndex];
if (SrcVertex.Position.Equals(DestVertex.Position, THRESH_POINTS_ARE_SAME) &&
SrcVertex.UVs[0].Equals(DestVertex.UVs[0], THRESH_UVS_ARE_SAME) &&
(SrcVertex.TangentX == DestVertex.TangentX) &&
(SrcVertex.TangentY == DestVertex.TangentY) &&
(SrcVertex.TangentZ == DestVertex.TangentZ))
{
MatchingSrcWedge = SrcIndex;
break;
}
}
if (MatchingSrcWedge == INDEX_NONE)
{
// We have to find the nearest wedges, then find the most similar normal
float MinDistance = MAX_FLT;
float MinNormalAngle = MAX_FLT;
for (FWedgeInfo& SrcWedgeInfo: NearestSrcWedges)
{
int32 SrcIndex = SrcWedgeInfo.WedgeIndex;
const FSoftSkinVertex& SrcVertex = SrcVertices[SrcIndex];
float VectorDelta = FVector::DistSquared(SrcVertex.Position, DestVertex.Position);
if (VectorDelta <= (MinDistance + KINDA_SMALL_NUMBER))
{
if (VectorDelta < MinDistance - KINDA_SMALL_NUMBER)
{
MinDistance = VectorDelta;
MinNormalAngle = MAX_FLT;
}
FVector DestTangentZ = DestVertex.TangentZ;
DestTangentZ.Normalize();
FVector SrcTangentZ = SrcVertex.TangentZ;
SrcTangentZ.Normalize();
float AngleDiff = FMath::Abs(FMath::Acos(FVector::DotProduct(DestTangentZ, SrcTangentZ)));
if (AngleDiff < MinNormalAngle)
{
MinNormalAngle = AngleDiff;
MatchingSrcWedge = SrcIndex;
}
}
}
}
check(SrcVertices.IsValidIndex(MatchingSrcWedge));
const FSoftSkinVertex& SrcVertex = SrcVertices[MatchingSrcWedge];
// Find the src section to assign the correct remapped bone
int32 SrcSectionIndex = INDEX_NONE;
int32 SrcSectionWedgeIndex = INDEX_NONE;
SrcLODModel.GetSectionFromVertexIndex(MatchingSrcWedge, SrcSectionIndex, SrcSectionWedgeIndex);
check(SrcSectionIndex != INDEX_NONE);
for (int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
{
if (SrcVertex.InfluenceWeights[InfluenceIndex] > 0.0f)
{
Section.MaxBoneInfluences = FMath::Max(Section.MaxBoneInfluences, InfluenceIndex + 1);
// Copy the weight
DestVertex.InfluenceWeights[InfluenceIndex] = SrcVertex.InfluenceWeights[InfluenceIndex];
// Copy the bone ID
FBoneIndexType OriginalBoneIndex = SrcLODModel.Sections[SrcSectionIndex].BoneMap[SrcVertex.InfluenceBones[InfluenceIndex]];
int32 OverrideIndex;
if (Section.BoneMap.Find(OriginalBoneIndex, OverrideIndex))
{
DestVertex.InfluenceBones[InfluenceIndex] = OverrideIndex;
}
else
{
DestVertex.InfluenceBones[InfluenceIndex] = Section.BoneMap.Add(OriginalBoneIndex);
DestLODModel.ActiveBoneIndices.AddUnique(OriginalBoneIndex);
}
bUseBone = true;
}
}
}
}
if (bUseBone)
{
// Set the required/active bones
DestLODModel.RequiredBones = SrcLODModel.RequiredBones;
DestLODModel.RequiredBones.Sort();
SkeletalMesh->GetRefSkeleton().EnsureParentsExistAndSort(DestLODModel.ActiveBoneIndices);
}
}
void SkeletalMeshImportUtils::RestoreExistingSkelMeshData(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh, int32 ReimportLODIndex, bool bCanShowDialog, bool bImportSkinningOnly, bool bForceMaterialReset)
{
using namespace SkeletalMesUtilsImpl;
if (!MeshData || !SkeletalMesh)
{
return;
}
// Restore the package metadata
InternalImportUtils::RestoreMetaData(SkeletalMesh, MeshData->ExistingUMetaDataTagValues);
int32 SafeReimportLODIndex = ReimportLODIndex < 0 ? 0 : ReimportLODIndex;
SkeletalMesh->SetMinLod(MeshData->MinLOD);
SkeletalMesh->SetDisableBelowMinLodStripping(MeshData->DisableBelowMinLodStripping);
SkeletalMesh->SetOverrideLODStreamingSettings(MeshData->bOverrideLODStreamingSettings);
SkeletalMesh->SetSupportLODStreaming(MeshData->bSupportLODStreaming);
SkeletalMesh->SetMaxNumStreamedLODs(MeshData->MaxNumStreamedLODs);
SkeletalMesh->SetMaxNumOptionalLODs(MeshData->MaxNumOptionalLODs);
FSkeletalMeshModel* SkeletalMeshImportedModel = SkeletalMesh->GetImportedModel();
// Create a remap material Index use to find the matching section later
TArray<int32> RemapMaterial;
RemapMaterial.AddZeroed(SkeletalMesh->GetMaterials().Num());
TArray<FName> RemapMaterialName;
RemapMaterialName.AddZeroed(SkeletalMesh->GetMaterials().Num());
bool bMaterialReset = false;
if (MeshData->bSaveRestoreMaterials)
{
UnFbx::EFBXReimportDialogReturnOption ReturnOption;
// Ask the user to match the materials conflict
UnFbx::FFbxImporter::PrepareAndShowMaterialConflictDialog<FSkeletalMaterial>(MeshData->ExistingMaterials, SkeletalMesh->GetMaterials(), RemapMaterial, RemapMaterialName, bCanShowDialog, false, bForceMaterialReset, ReturnOption);
if (ReturnOption != UnFbx::EFBXReimportDialogReturnOption::FBXRDRO_ResetToFbx)
{
// Build a ordered material list that try to keep intact the existing material list
TArray<FSkeletalMaterial> MaterialOrdered;
TArray<bool> MatchedNewMaterial;
MatchedNewMaterial.AddZeroed(SkeletalMesh->GetMaterials().Num());
for (int32 ExistMaterialIndex = 0; ExistMaterialIndex < MeshData->ExistingMaterials.Num(); ++ExistMaterialIndex)
{
int32 MaterialIndexOrdered = MaterialOrdered.Add(MeshData->ExistingMaterials[ExistMaterialIndex]);
FSkeletalMaterial& OrderedMaterial = MaterialOrdered[MaterialIndexOrdered];
int32 NewMaterialIndex = INDEX_NONE;
if (RemapMaterial.Find(ExistMaterialIndex, NewMaterialIndex))
{
MatchedNewMaterial[NewMaterialIndex] = true;
RemapMaterial[NewMaterialIndex] = MaterialIndexOrdered;
OrderedMaterial.ImportedMaterialSlotName = SkeletalMesh->GetMaterials()[NewMaterialIndex].ImportedMaterialSlotName;
}
else
{
// Unmatched material must be conserve
}
}
// Add the new material entries (the one that do not match with any existing material)
for (int32 NewMaterialIndex = 0; NewMaterialIndex < MatchedNewMaterial.Num(); ++NewMaterialIndex)
{
if (MatchedNewMaterial[NewMaterialIndex] == false)
{
int32 NewMeshIndex = MaterialOrdered.Add(SkeletalMesh->GetMaterials()[NewMaterialIndex]);
RemapMaterial[NewMaterialIndex] = NewMeshIndex;
}
}
// Set the RemapMaterialName array helper
for (int32 MaterialIndex = 0; MaterialIndex < RemapMaterial.Num(); ++MaterialIndex)
{
int32 SourceMaterialMatch = RemapMaterial[MaterialIndex];
if (MeshData->ExistingMaterials.IsValidIndex(SourceMaterialMatch))
{
RemapMaterialName[MaterialIndex] = MeshData->ExistingMaterials[SourceMaterialMatch].ImportedMaterialSlotName;
}
}
// Copy the re ordered materials (this ensure the material array do not change when we re-import)
SkeletalMesh->SetMaterials(MaterialOrdered);
}
else
{
bMaterialReset = true;
}
}
SkeletalMesh->SetLODSettings(MeshData->ExistingLODSettings);
// ensure LOD 0 contains correct setting
if (SkeletalMesh->GetLODSettings() && SkeletalMesh->GetLODInfoArray().Num() > 0)
{
SkeletalMesh->GetLODSettings()->SetLODSettingsToMesh(SkeletalMesh, 0);
}
// Do everything we need for base LOD re-import
if (SafeReimportLODIndex == 0)
{
// this is not ideal. Ideally we'll have to save only diff with indicating which joints,
// but for now, we allow them to keep the previous pose IF the element count is same
if (MeshData->ExistingRetargetBasePose.Num() == SkeletalMesh->GetRefSkeleton().GetRawBoneNum())
{
SkeletalMesh->SetRetargetBasePose(MeshData->ExistingRetargetBasePose);
}
// Assign sockets from old version of this SkeletalMesh.
// Only copy ones for bones that exist in the new mesh.
for (int32 i = 0; i < MeshData->ExistingSockets.Num(); i++)
{
const int32 BoneIndex = SkeletalMesh->GetRefSkeleton().FindBoneIndex(MeshData->ExistingSockets[i]->BoneName);
if (BoneIndex != INDEX_NONE)
{
SkeletalMesh->GetMeshOnlySocketList().Add(MeshData->ExistingSockets[i]);
}
}
// We copy back and fix-up the LODs that still work with this skeleton.
if (MeshData->ExistingLODModels.Num() > 1)
{
auto RestoreReductionSourceData = [&SkeletalMesh, &SkeletalMeshImportedModel, &MeshData](int32 ExistingIndex, int32 NewIndex)
{
if (!MeshData->ExistingOriginalReductionSourceMeshData[ExistingIndex].IsValid() || MeshData->ExistingOriginalReductionSourceMeshData[ExistingIndex]->IsEmpty())
{
return;
}
// Restore the original reduction source mesh data
FSkeletalMeshLODModel BaseLODModel;
TMap<FString, TArray<FMorphTargetDelta>> BaseLODMorphTargetData;
MeshData->ExistingOriginalReductionSourceMeshData[ExistingIndex]->LoadReductionData(BaseLODModel, BaseLODMorphTargetData, SkeletalMesh);
FReductionBaseSkeletalMeshBulkData* ReductionLODData = new FReductionBaseSkeletalMeshBulkData();
ReductionLODData->SaveReductionData(BaseLODModel, BaseLODMorphTargetData, SkeletalMesh);
// Add necessary empty slot
while (SkeletalMeshImportedModel->OriginalReductionSourceMeshData.Num() < NewIndex)
{
FReductionBaseSkeletalMeshBulkData* EmptyReductionLODData = new FReductionBaseSkeletalMeshBulkData();
SkeletalMeshImportedModel->OriginalReductionSourceMeshData.Add(EmptyReductionLODData);
}
SkeletalMeshImportedModel->OriginalReductionSourceMeshData.Add(ReductionLODData);
};
if (SkeletonsAreCompatible(SkeletalMesh->GetRefSkeleton(), MeshData->ExistingRefSkeleton, bImportSkinningOnly))
{
// First create mapping table from old skeleton to new skeleton.
TArray<int32> OldToNewMap;
OldToNewMap.AddUninitialized(MeshData->ExistingRefSkeleton.GetRawBoneNum());
for (int32 i = 0; i < MeshData->ExistingRefSkeleton.GetRawBoneNum(); i++)
{
OldToNewMap[i] = SkeletalMesh->GetRefSkeleton().FindBoneIndex(MeshData->ExistingRefSkeleton.GetBoneName(i));
}
// Starting at index 1 because we only need to add LOD models of LOD 1 and higher.
for (int32 LODIndex = 1; LODIndex < MeshData->ExistingLODModels.Num(); ++LODIndex)
{
FSkeletalMeshLODModel* LODModelCopy = FSkeletalMeshLODModel::CreateCopy(&MeshData->ExistingLODModels[LODIndex]);
const FSkeletalMeshLODInfo& LODInfo = MeshData->ExistingLODInfo[LODIndex];
// Fix ActiveBoneIndices array.
bool bMissingBone = false;
FName MissingBoneName = NAME_None;
for (int32 j = 0; j < LODModelCopy->ActiveBoneIndices.Num() && !bMissingBone; j++)
{
int32 OldActiveBoneIndex = LODModelCopy->ActiveBoneIndices[j];
if (OldToNewMap.IsValidIndex(OldActiveBoneIndex))
{
int32 NewBoneIndex = OldToNewMap[OldActiveBoneIndex];
if (NewBoneIndex == INDEX_NONE)
{
bMissingBone = true;
MissingBoneName = MeshData->ExistingRefSkeleton.GetBoneName(LODModelCopy->ActiveBoneIndices[j]);
}
else
{
LODModelCopy->ActiveBoneIndices[j] = NewBoneIndex;
}
}
else
{
LODModelCopy->ActiveBoneIndices.RemoveAt(j, 1, false);
--j;
}
}
// Fix RequiredBones array.
for (int32 j = 0; j < LODModelCopy->RequiredBones.Num() && !bMissingBone; j++)
{
const int32 OldBoneIndex = LODModelCopy->RequiredBones[j];
if (OldToNewMap.IsValidIndex(OldBoneIndex)) // Previously virtual bones could end up in this array
// Must validate against this
{
const int32 NewBoneIndex = OldToNewMap[OldBoneIndex];
if (NewBoneIndex == INDEX_NONE)
{
bMissingBone = true;
MissingBoneName = MeshData->ExistingRefSkeleton.GetBoneName(OldBoneIndex);
}
else
{
LODModelCopy->RequiredBones[j] = NewBoneIndex;
}
}
else
{
// Bone didn't exist in our required bones, clean up.
LODModelCopy->RequiredBones.RemoveAt(j, 1, false);
--j;
}
}
// Sort ascending for parent child relationship
LODModelCopy->RequiredBones.Sort();
SkeletalMesh->GetRefSkeleton().EnsureParentsExistAndSort(LODModelCopy->ActiveBoneIndices);
// Fix the sections' BoneMaps.
for (int32 SectionIndex = 0; SectionIndex < LODModelCopy->Sections.Num(); SectionIndex++)
{
FSkelMeshSection& Section = LODModelCopy->Sections[SectionIndex];
for (int32 BoneIndex = 0; BoneIndex < Section.BoneMap.Num(); BoneIndex++)
{
int32 NewBoneIndex = OldToNewMap[Section.BoneMap[BoneIndex]];
if (NewBoneIndex == INDEX_NONE)
{
bMissingBone = true;
MissingBoneName = MeshData->ExistingRefSkeleton.GetBoneName(Section.BoneMap[BoneIndex]);
break;
}
else
{
Section.BoneMap[BoneIndex] = NewBoneIndex;
}
}
if (bMissingBone)
{
break;
}
}
if (bMissingBone)
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("NewMeshMissingBoneFromLOD", "New mesh is missing bone '{0}' required by an LOD."), FText::FromName(MissingBoneName))), FFbxErrors::SkeletalMesh_LOD_MissingBone);
break;
}
else
{
// We need to add LODInfo
SkeletalMeshImportedModel->LODModels.Add(LODModelCopy);
SkeletalMesh->AddLODInfo(LODInfo);
RestoreReductionSourceData(LODIndex, SkeletalMesh->GetLODNum() - 1);
}
}
}
// We just need to restore the LOD model and LOD info the build should regenerate the LODs
RestoreDependentLODs(MeshData, SkeletalMesh);
// Old asset cannot use the new build system, we need to regenerate dependent LODs
if (SkeletalMesh->IsLODImportedDataBuildAvailable(SafeReimportLODIndex) == false)
{
FLODUtilities::RegenerateDependentLODs(SkeletalMesh, SafeReimportLODIndex, GetTargetPlatformManagerRef().GetRunningTargetPlatform());
}
}
for (int32 AssetIndex = 0; AssetIndex < MeshData->ExistingPhysicsAssets.Num(); ++AssetIndex)
{
UPhysicsAsset* PhysicsAsset = MeshData->ExistingPhysicsAssets[AssetIndex];
if (AssetIndex == 0)
{
// First asset is the one that the skeletal mesh should point too
SkeletalMesh->SetPhysicsAsset(PhysicsAsset);
}
// No need to mark as modified here, because the asset hasn't actually changed
if (PhysicsAsset)
{
PhysicsAsset->PreviewSkeletalMesh = SkeletalMesh;
}
}
SkeletalMesh->SetShadowPhysicsAsset(MeshData->ExistingShadowPhysicsAsset);
SkeletalMesh->SetSkeleton(MeshData->ExistingSkeleton);
SkeletalMesh->SetPostProcessAnimBlueprint(MeshData->ExistingPostProcessAnimBlueprint);
SkeletalMesh->SetDefaultAnimatingRig(MeshData->ExistingDefaultAnimatingRig);
// Copy mirror table.
SkeletalMesh->ImportMirrorTable(MeshData->ExistingMirrorTable);
SkeletalMesh->GetMorphTargets().Empty(MeshData->ExistingMorphTargets.Num());
SkeletalMesh->GetMorphTargets().Append(MeshData->ExistingMorphTargets);
SkeletalMesh->InitMorphTargets();
SkeletalMesh->SetAssetImportData(MeshData->ExistingAssetImportData.Get());
SkeletalMesh->SetThumbnailInfo(MeshData->ExistingThumbnailInfo.Get());
SkeletalMesh->SetMeshClothingAssets(MeshData->ExistingClothingAssets);
for (UClothingAssetBase* ClothingAsset: SkeletalMesh->GetMeshClothingAssets())
{
if (ClothingAsset)
{
ClothingAsset->RefreshBoneMapping(SkeletalMesh);
}
}
SkeletalMesh->SetSamplingInfo(MeshData->ExistingSamplingInfo);
}
// Restore the section change only for the reimport LOD, other LOD are not affected since the material array can only grow.
if (MeshData->UseMaterialNameSlotWorkflow)
{
RestoreMaterialNameWorkflowSection(MeshData, SkeletalMesh, SafeReimportLODIndex, RemapMaterial, bMaterialReset);
}
// Copy back the reimported LOD's specific data
if (SkeletalMesh->GetLODInfoArray().IsValidIndex(SafeReimportLODIndex))
{
RestoreLODInfo(MeshData, SkeletalMesh, SafeReimportLODIndex);
}
// Copy user data to newly created mesh
for (auto Kvp: MeshData->ExistingAssetUserData)
{
UAssetUserData* UserDataObject = Kvp.Key;
if (Kvp.Value)
{
// if the duplicated temporary UObject was add to root, we must remove it from the root
UserDataObject->RemoveFromRoot();
}
UserDataObject->Rename(nullptr, SkeletalMesh, REN_DontCreateRedirectors | REN_DoNotDirty);
SkeletalMesh->AddAssetUserData(UserDataObject);
}
if (!bImportSkinningOnly && (!MeshData->ExistingLODInfo.IsValidIndex(SafeReimportLODIndex) || !MeshData->ExistingLODInfo[SafeReimportLODIndex].bHasBeenSimplified))
{
if (SkeletalMeshImportedModel->OriginalReductionSourceMeshData.IsValidIndex(SafeReimportLODIndex))
{
SkeletalMeshImportedModel->OriginalReductionSourceMeshData[SafeReimportLODIndex]->EmptyBulkData();
}
}
// Copy mesh changed delegate data
SkeletalMesh->GetOnMeshChanged() = MeshData->ExistingOnMeshChanged;
// Copy mesh bounds extensions
SkeletalMesh->SetPositiveBoundsExtension(MeshData->PositiveBoundsExtension);
SkeletalMesh->SetNegativeBoundsExtension(MeshData->NegativeBoundsExtension);
}
void SkeletalMesUtilsImpl::RestoreMaterialNameWorkflowSection(const TSharedPtr<const FExistingSkelMeshData>& MeshData, USkeletalMesh* SkeletalMesh, int32 LodIndex, TArray<int32>& RemapMaterial, bool bMaterialReset)
{
FSkeletalMeshModel* SkeletalMeshImportedModel = SkeletalMesh->GetImportedModel();
FSkeletalMeshLODModel& SkeletalMeshLodModel = SkeletalMeshImportedModel->LODModels[LodIndex];
// Restore the base LOD materialMap the LODs LODMaterialMap are restore differently
if (LodIndex == 0 && SkeletalMesh->GetLODInfoArray().IsValidIndex(LodIndex))
{
FSkeletalMeshLODInfo& BaseLODInfo = SkeletalMesh->GetLODInfoArray()[LodIndex];
if (bMaterialReset)
{
// If we reset the material array there is no point keeping the user changes
BaseLODInfo.LODMaterialMap.Empty();
}
else if (SkeletalMeshImportedModel->LODModels.IsValidIndex(LodIndex))
{
// Restore the Base MaterialMap
for (int32 SectionIndex = 0; SectionIndex < SkeletalMeshLodModel.Sections.Num(); ++SectionIndex)
{
int32 MaterialIndex = SkeletalMeshLodModel.Sections[SectionIndex].MaterialIndex;
if (MeshData->ExistingLODInfo[LodIndex].LODMaterialMap.IsValidIndex(SectionIndex))
{
int32 ExistingLODMaterialIndex = MeshData->ExistingLODInfo[LodIndex].LODMaterialMap[SectionIndex];
while (BaseLODInfo.LODMaterialMap.Num() <= SectionIndex)
{
BaseLODInfo.LODMaterialMap.Add(INDEX_NONE);
}
BaseLODInfo.LODMaterialMap[SectionIndex] = ExistingLODMaterialIndex;
}
}
}
}
const bool bIsValidSavedSectionMaterialData = MeshData->ExistingImportMeshLodSectionMaterialData.IsValidIndex(LodIndex) && MeshData->LastImportMeshLodSectionMaterialData.IsValidIndex(LodIndex);
const int32 MaxExistSectionNumber = bIsValidSavedSectionMaterialData ? FMath::Max(MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex].Num(), MeshData->LastImportMeshLodSectionMaterialData[LodIndex].Num()) : 0;
TBitArray<> MatchedExistSectionIndex;
MatchedExistSectionIndex.Init(false, MaxExistSectionNumber);
const TArray<FSkeletalMaterial>& SkeletalMeshMaterials = SkeletalMesh->GetMaterials();
// Restore the section changes from the old import data
for (int32 SectionIndex = 0; SectionIndex < SkeletalMeshLodModel.Sections.Num(); SectionIndex++)
{
// Find the import section material index by using the RemapMaterial array. Fallback on the imported index if the remap entry is not valid
FSkelMeshSection& NewSection = SkeletalMeshLodModel.Sections[SectionIndex];
int32 RemapMaterialIndex = RemapMaterial.IsValidIndex(NewSection.MaterialIndex) ? RemapMaterial[NewSection.MaterialIndex] : NewSection.MaterialIndex;
if (!SkeletalMeshMaterials.IsValidIndex(RemapMaterialIndex))
{
// We have an invalid material section, in this case we set the material index to 0
NewSection.MaterialIndex = 0;
UE_LOG(LogSkeletalMeshImport, Display, TEXT("Reimport material match issue: Invalid RemapMaterialIndex [%d], will make it point to material index [0]"), RemapMaterialIndex);
continue;
}
NewSection.MaterialIndex = RemapMaterialIndex;
// skip the rest of the loop if we do not have valid saved data
if (!bIsValidSavedSectionMaterialData)
{
continue;
}
// Get the RemapMaterial section Imported material slot name. We need it to match the saved existing section, so we can put back the saved existing section data
FName CurrentSectionImportedMaterialName = SkeletalMeshMaterials[RemapMaterialIndex].ImportedMaterialSlotName;
for (int32 ExistSectionIndex = 0; ExistSectionIndex < MaxExistSectionNumber; ++ExistSectionIndex)
{
// Skip already matched exist section
if (MatchedExistSectionIndex[ExistSectionIndex])
{
continue;
}
// Verify we have valid existing section data, if not break from the loop higher index wont be valid
if (!MeshData->LastImportMeshLodSectionMaterialData[LodIndex].IsValidIndex(ExistSectionIndex) || !MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex].IsValidIndex(ExistSectionIndex))
{
break;
}
// Get the Last imported skelmesh section slot import name
FName OriginalImportMeshSectionSlotName = MeshData->LastImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex];
if (OriginalImportMeshSectionSlotName != CurrentSectionImportedMaterialName)
{
// Skip until we found a match between the last import
continue;
}
// We have a match put back the data
NewSection.bCastShadow = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].bCastShadow;
NewSection.bRecomputeTangent = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].bRecomputeTangents;
NewSection.RecomputeTangentsVertexMaskChannel = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].RecomputeTangentsVertexMaskChannel;
NewSection.GenerateUpToLodIndex = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].GenerateUpTo;
NewSection.bDisabled = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].bDisabled;
bool bBoneChunkedSection = NewSection.ChunkedParentSectionIndex >= 0;
int32 ParentOriginalSectionIndex = NewSection.OriginalDataSectionIndex;
if (!bBoneChunkedSection)
{
// Set the new Parent Index
FSkelMeshSourceSectionUserData& UserSectionData = SkeletalMeshLodModel.UserSectionsData.FindOrAdd(ParentOriginalSectionIndex);
UserSectionData.bDisabled = NewSection.bDisabled;
UserSectionData.bCastShadow = NewSection.bCastShadow;
UserSectionData.bRecomputeTangent = NewSection.bRecomputeTangent;
UserSectionData.RecomputeTangentsVertexMaskChannel = NewSection.RecomputeTangentsVertexMaskChannel;
UserSectionData.GenerateUpToLodIndex = NewSection.GenerateUpToLodIndex;
// The cloth will be rebind later after the reimport is done
}
// Set the matched section to true to avoid using it again
MatchedExistSectionIndex[ExistSectionIndex] = true;
// find the corresponding current slot name in the skeletal mesh materials list to remap properly the material index, in case the user have change it before re-importing
FName ExistMeshSectionSlotName = MeshData->ExistingImportMeshLodSectionMaterialData[LodIndex][ExistSectionIndex].ImportedMaterialSlotName;
{
for (int32 SkelMeshMaterialIndex = 0; SkelMeshMaterialIndex < SkeletalMeshMaterials.Num(); ++SkelMeshMaterialIndex)
{
const FSkeletalMaterial& NewSectionMaterial = SkeletalMeshMaterials[SkelMeshMaterialIndex];
if (NewSectionMaterial.ImportedMaterialSlotName == ExistMeshSectionSlotName)
{
if (ExistMeshSectionSlotName != OriginalImportMeshSectionSlotName)
{
NewSection.MaterialIndex = SkelMeshMaterialIndex;
}
break;
}
}
}
// Break because we found a match and have restore the data for this SectionIndex
break;
}
}
// Make sure we reset the User section array to only what we have in the fbx
SkeletalMeshLodModel.SyncronizeUserSectionsDataArray(true);
}
#undef LOCTEXT_NAMESPACE