I managed to have something which work. I’ll put the code here since it could be useful for others. Use it at your own risk since it has not been thoroughly tested and it’s mostly a lot of fixes and patchworks added on top of the code provided in the documentation.
Basically I tried to create the ImportMesh data back from the generated RenderedData and the input meshes to merge. It seems to work fine for my use case which concerns simple skeletal meshes.
Here is the code :
(Note that the rest of the code is the same as the one in the documentation so I won’t paste it here)
USkeletalMesh* UMeshMergeFunctionLibrary::MergeMeshes(const FSkeletalMeshMergeParams& Params, const FString& PackagePath, const FString& AssetName)
{
TArray<USkeletalMesh*> MeshesToMergeCopy = Params.MeshesToMerge;
MeshesToMergeCopy.RemoveAll(](USkeletalMesh* InMesh)
{
return InMesh == nullptr;
});
if (MeshesToMergeCopy.Num() <= 1)
{
UE_LOG(LogTemp, Warning, TEXT("Must provide multiple valid Skeletal Meshes in order to perform a merge."));
return nullptr;
}
EMeshBufferAccess BufferAccess = Params.bNeedsCpuAccess ?
EMeshBufferAccess::ForceCPUAndGPU :
EMeshBufferAccess::Default;
TArray<FSkelMeshMergeSectionMapping> SectionMappings;
TArray<FSkelMeshMergeUVTransforms> UvTransforms;
ToMergeParams(Params.MeshSectionMappings, SectionMappings);
ToMergeParams(Params.UVTransformsPerMesh, UvTransforms);
bool bRunDuplicateCheck = false;
IAssetTools& assetToolModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
FString formatedAssetName;
FString PackageName = PackagePath + "/" + AssetName;
assetToolModule.CreateUniqueAssetName(PackageName, "", PackageName, formatedAssetName);
UPackage* Package = CreatePackage(NULL, *PackageName);
if (!Package)
return nullptr;
Package->FullyLoad();
USkeletalMesh* BaseMesh = NewObject<USkeletalMesh>(Package, *AssetName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
if (!BaseMesh)
return nullptr;
if (Params.Skeleton && Params.bSkeletonBefore)
{
BaseMesh->Skeleton = Params.Skeleton;
bRunDuplicateCheck = true;
for (USkeletalMeshSocket* Socket : BaseMesh->GetMeshOnlySocketList())
{
if (Socket)
{
UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocket: %s"), *(Socket->SocketName.ToString()));
}
}
for (USkeletalMeshSocket* Socket : BaseMesh->Skeleton->Sockets)
{
if (Socket)
{
UE_LOG(LogTemp, Warning, TEXT("SkelSocket: %s"), *(Socket->SocketName.ToString()));
}
}
}
FSkeletalMeshMerge Merger(BaseMesh, MeshesToMergeCopy, SectionMappings, Params.StripTopLODS, BufferAccess, UvTransforms.GetData());
if (!Merger.DoMerge())
{
UE_LOG(LogTemp, Warning, TEXT("Merge failed!"));
return nullptr;
}
if (Params.Skeleton && !Params.bSkeletonBefore)
{
BaseMesh->Skeleton = Params.Skeleton;
}
if (bRunDuplicateCheck)
{
TArray<FName> SkelMeshSockets;
TArray<FName> SkelSockets;
for (USkeletalMeshSocket* Socket : BaseMesh->GetMeshOnlySocketList())
{
if (Socket)
{
SkelMeshSockets.Add(Socket->GetFName());
UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocket: %s"), *(Socket->SocketName.ToString()));
}
}
for (USkeletalMeshSocket* Socket : BaseMesh->Skeleton->Sockets)
{
if (Socket)
{
SkelSockets.Add(Socket->GetFName());
UE_LOG(LogTemp, Warning, TEXT("SkelSocket: %s"), *(Socket->SocketName.ToString()));
}
}
TSet<FName> UniqueSkelMeshSockets;
TSet<FName> UniqueSkelSockets;
UniqueSkelMeshSockets.Append(SkelMeshSockets);
UniqueSkelSockets.Append(SkelSockets);
int32 Total = SkelSockets.Num() + SkelMeshSockets.Num();
int32 UniqueTotal = UniqueSkelMeshSockets.Num() + UniqueSkelSockets.Num();
UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocketCount: %d | SkelSocketCount: %d | Combined: %d"), SkelMeshSockets.Num(), SkelSockets.Num(), Total);
UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocketCount: %d | SkelSocketCount: %d | Combined: %d"), UniqueSkelMeshSockets.Num(), UniqueSkelSockets.Num(), UniqueTotal);
UE_LOG(LogTemp, Warning, TEXT("Found Duplicates: %s"), *((Total != UniqueTotal) ? FString("True") : FString("False")));
}
// Generate the imported model data (or try to...)
FSkeletalMeshModel* skMeshModel = BaseMesh->GetImportedModel();
{
FSkeletalMeshRenderData* renderData = BaseMesh->GetResourceForRendering();
for (int32 lodIdx = 0; lodIdx < renderData->LODRenderData.Num(); lodIdx++)
{
FSkeletalMeshLODModel* skMeshLODModel = new FSkeletalMeshLODModel();
{
FSkeletalMeshLODRenderData* mainLODRenderData = &renderData->LODRenderData[lodIdx];
TArray<uint32> indexBuffer;
indexBuffer.Reserve(mainLODRenderData->MultiSizeIndexContainer.GetIndexBuffer()->Num());
for (int32 i = 0; i < mainLODRenderData->MultiSizeIndexContainer.GetIndexBuffer()->Num(); i++)
{
indexBuffer.Add(mainLODRenderData->MultiSizeIndexContainer.GetIndexBuffer()->Get(i));
}
TArray<FSkelMeshSection> sections;
sections.Reserve(mainLODRenderData->RenderSections.Num());
for (int32 i = 0; i < mainLODRenderData->RenderSections.Num(); i++)
{
const FSkelMeshRenderSection& renderSection = mainLODRenderData->RenderSections*;
FSkelMeshSection section;
section.BaseIndex = renderSection.BaseIndex;
section.BaseVertexIndex = renderSection.BaseVertexIndex;
section.bCastShadow = renderSection.bCastShadow;
section.bDisabled = renderSection.bDisabled;
section.BoneMap = renderSection.BoneMap;
section.bRecomputeTangent = renderSection.bRecomputeTangent;
section.bSelected = false;
section.ClothingData = renderSection.ClothingData;
section.ClothMappingData = renderSection.ClothMappingData;
section.CorrespondClothAssetIndex = renderSection.CorrespondClothAssetIndex;
section.GenerateUpToLodIndex = -1;
section.MaterialIndex = renderSection.MaterialIndex;
section.MaxBoneInfluences = renderSection.MaxBoneInfluences;
section.NumTriangles = renderSection.NumTriangles;
section.NumVertices = renderSection.NumVertices;
// section.OverlappingVertices;
{
int32 firstVertexIdx = 0;
TArray<FSoftSkinVertex> vertices;
for (auto& meshToMerge : MeshesToMergeCopy)
{
TArray<FSoftSkinVertex> tmpVertices;
meshToMerge->GetImportedModel()->LODModels[lodIdx].GetVertices(tmpVertices);
int32 curVertexIdx = 0;
for (FSoftSkinVertex& tmpVertex : tmpVertices)
{
const bool hasExtraBoneInfluence = mainLODRenderData->GetSkinWeightVertexBuffer()->HasExtraBoneInfluences();
if (hasExtraBoneInfluence)
{
const TSkinWeightInfo<true>* SrcSkinWeights = mainLODRenderData->GetSkinWeightVertexBuffer()->GetSkinWeightPtr<true>(curVertexIdx + firstVertexIdx);
// if source doesn't have extra influence, we have to clear the buffer
FMemory::Memzero(tmpVertex.InfluenceBones);
FMemory::Memzero(tmpVertex.InfluenceWeights);
FMemory::Memcpy(tmpVertex.InfluenceBones, SrcSkinWeights->InfluenceBones, sizeof(SrcSkinWeights->InfluenceBones));
FMemory::Memcpy(tmpVertex.InfluenceWeights, SrcSkinWeights->InfluenceWeights, sizeof(SrcSkinWeights->InfluenceWeights));
}
else
{
const TSkinWeightInfo<false>* SrcSkinWeights = mainLODRenderData->GetSkinWeightVertexBuffer()->GetSkinWeightPtr<false>(curVertexIdx + firstVertexIdx);
// if source doesn't have extra influence, we have to clear the buffer
FMemory::Memzero(tmpVertex.InfluenceBones);
FMemory::Memzero(tmpVertex.InfluenceWeights);
FMemory::Memcpy(tmpVertex.InfluenceBones, SrcSkinWeights->InfluenceBones, sizeof(SrcSkinWeights->InfluenceBones));
FMemory::Memcpy(tmpVertex.InfluenceWeights, SrcSkinWeights->InfluenceWeights, sizeof(SrcSkinWeights->InfluenceWeights));
}
curVertexIdx++;
}
vertices.Append(tmpVertices);
firstVertexIdx += tmpVertices.Num();
}
section.SoftVertices = vertices;
}
sections.Add(section);
}
//------------------------------------
skMeshLODModel->ActiveBoneIndices = mainLODRenderData->ActiveBoneIndices;
skMeshLODModel->IndexBuffer = indexBuffer;
skMeshLODModel->MaxImportVertex = mainLODRenderData->GetNumVertices();
//skMeshLODModel->MeshToImportVertexMap = ;
skMeshLODModel->NumTexCoords = mainLODRenderData->GetNumTexCoords();
skMeshLODModel->NumVertices = mainLODRenderData->GetNumVertices();
//skMeshLODModel->RawPointIndices = ;
//skMeshLODModel->RawSkeletalMeshBulkData = ;
skMeshLODModel->RequiredBones = mainLODRenderData->RequiredBones;
skMeshLODModel->Sections = sections;
TMap<FName, FImportedSkinWeightProfileData> mergedImportedSkinWeightProfile;
for (auto& meshToMerge : MeshesToMergeCopy)
{
mergedImportedSkinWeightProfile.Append(meshToMerge->GetImportedModel()->LODModels[lodIdx].SkinWeightProfiles);
}
for (const FSkinWeightProfileInfo& profile : BaseMesh->GetSkinWeightProfiles())
{
skMeshLODModel->SkinWeightProfiles.Add(profile.Name, *mergedImportedSkinWeightProfile.Find(profile.Name));
}
}
skMeshModel->LODModels.Add(skMeshLODModel);
}
}
//--------------------------------------------------------------
// register the package
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(BaseMesh);
//--------------------------------------------------------------
// Really dirty fix
UProperty* prop = FindFieldChecked<UProperty>(USkeletalMesh::StaticClass(), GET_MEMBER_NAME_CHECKED(USkeletalMesh, Skeleton));
FPropertyChangedEvent propChange(prop);
BaseMesh->PostEditChangeProperty(propChange);
//--------------------------------------------------------------
return BaseMesh;
}