// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License. #include "VrmConvertHumanoid.h" #include "VrmConvert.h" #include "VrmUtil.h" #include "VrmAssetListObject.h" #include "VrmMetaObject.h" #include "Engine/SkeletalMesh.h" #include "RenderingThread.h" #include "Rendering/SkeletalMeshModel.h" #include "Rendering/SkeletalMeshLODModel.h" #include "Rendering/SkeletalMeshLODRenderData.h" #include "Rendering/SkeletalMeshRenderData.h" #include "Animation/MorphTarget.h" #include "Animation/Skeleton.h" #include "Animation/NodeMappingContainer.h" #include #include #include #include #include #include static void renameToHumanoidBone(USkeletalMesh *targetSK, const UVrmMetaObject *meta, const USkeletalMesh *srcSK) { USkeleton *targetSkeleton = VRMGetSkeleton(targetSK); UNodeMappingContainer* rig = nullptr; if (VRMGetNodeMappingData(targetSK).Num()) { rig = VRMGetNodeMappingData(targetSK)[0]; } UNodeMappingContainer* src_rig = nullptr; if (VRMGetNodeMappingData(srcSK).Num()) { src_rig = VRMGetNodeMappingData(srcSK)[0]; } if (meta == nullptr) { return; } //k->RemoveBonesFromSkeleton() auto &allbone = const_cast &>(targetSkeleton->GetReferenceSkeleton().GetRawRefBoneInfo()); TMap changeTable; { bool bHasRoot = false; for (auto& a : allbone) { if (a.Name == TEXT("Root")) { a.Name = TEXT("root"); #if WITH_EDITORONLY_DATA a.ExportName = TEXT("root"); #endif bHasRoot = true; break; } } if (bHasRoot == false) { if (allbone[0].Name != TEXT("hip")) { allbone[0].Name = TEXT("root"); #if WITH_EDITORONLY_DATA allbone[0].ExportName = TEXT("root"); #endif } } } for (auto &a : allbone) { auto p = meta->humanoidBoneTable.FindKey(a.Name.ToString()); if (p == nullptr) { continue; } if (p->IsEmpty()) { continue; } for (auto &b : allbone) { if (a == b) continue; if (b.Name == **p) { b.Name = *(b.Name.ToString() + TEXT("_renamed_vrm4u")); } } changeTable.Add(a.Name, **p); a.Name = **p; #if WITH_EDITORONLY_DATA a.ExportName = **p; #endif } const_cast(targetSkeleton->GetReferenceSkeleton()).RebuildRefSkeleton(targetSkeleton, true); VRMSetRefSkeleton(targetSK, targetSkeleton->GetReferenceSkeleton()); #if WITH_EDITOR #if UE_VERSION_OLDER_THAN(4,20,0) #elif UE_VERSION_OLDER_THAN(5,4,0) if (src_rig) { rig->SetSourceAsset(src_rig->GetSourceAsset()); rig->SetTargetAsset(targetSK); targetSkeleton->RefreshRigConfig(); { auto &table = src_rig->GetNodeMappingTable(); for (auto &t : table) { FName n = t.Value; { auto *ret = changeTable.Find(t.Value); if (ret) { n = *ret; } } rig->AddMapping(t.Key, n); targetSkeleton->SetRigBoneMapping(t.Key, n); } } rig->PostEditChange(); } #else // ue5.4 no rig data #endif #endif } static void renameToUE4Bone(USkeletalMesh *targetSK, UVrmMetaObject *meta, const USkeletalMesh *srcSK) { USkeleton *targetSkeleton = VRMGetSkeleton(targetSK); UNodeMappingContainer* rig = nullptr; if (VRMGetNodeMappingData(targetSK).Num()) { rig = VRMGetNodeMappingData(targetSK)[0]; } UNodeMappingContainer* src_rig = nullptr; if (VRMGetNodeMappingData(srcSK).Num()) { src_rig = VRMGetNodeMappingData(srcSK)[0]; } //k->RemoveBonesFromSkeleton() auto &allbone = const_cast &>(targetSkeleton->GetReferenceSkeleton().GetRawRefBoneInfo()); TMap changeTable; allbone[0].Name = TEXT("root"); #if WITH_EDITORONLY_DATA allbone[0].ExportName = TEXT("root"); #endif for (auto &a : allbone) { FString toVRMBone; for (auto& tmp : meta->humanoidBoneTable) { if (a.Name.ToString().ToLower() != tmp.Value.ToLower()) continue; toVRMBone = tmp.Key; break; } if (toVRMBone == "") continue; FString toUE4Bone; for (auto& tmp : VRMUtil::table_ue4_vrm) { if (toVRMBone.ToLower() != tmp.BoneVRM.ToLower()) continue; toUE4Bone = tmp.BoneUE4; break; } if (toUE4Bone == "") continue; FString newName = toUE4Bone; for (auto &b : allbone) { if (a == b) continue; if (b.Name == *(newName.ToLower())) { b.Name = *(b.Name.ToString() + TEXT("_renamed_vrm4u")); } } changeTable.Add(a.Name, *newName); a.Name = *newName; #if WITH_EDITORONLY_DATA a.ExportName = *newName; #endif } const_cast(targetSkeleton->GetReferenceSkeleton()).RebuildRefSkeleton(targetSkeleton, true); VRMSetRefSkeleton(targetSK, targetSkeleton->GetReferenceSkeleton()); #if WITH_EDITOR #if UE_VERSION_OLDER_THAN(4,20,0) #elif UE_VERSION_OLDER_THAN(5,4,0) if (src_rig) { rig->SetSourceAsset(src_rig->GetSourceAsset()); rig->SetTargetAsset(targetSK); targetSkeleton->RefreshRigConfig(); { auto &table = src_rig->GetNodeMappingTable(); for (auto &t : table) { FName n = t.Value; { auto *ret = changeTable.Find(t.Value); if (ret) { n = *ret; } } rig->AddMapping(t.Key, n); targetSkeleton->SetRigBoneMapping(t.Key, n); } } rig->PostEditChange(); } #else // ue5.4 no rig data #endif #endif } bool VRMConverter::ConvertHumanoid(UVrmAssetListObject *vrmAssetList) { if (Options::Get().IsGenerateHumanoidRenamedMesh() == false) { return true; } const USkeletalMesh *src_sk = vrmAssetList->SkeletalMesh; const USkeleton *src_k = VRMGetSkeleton(src_sk); const UNodeMappingContainer *src_rig = nullptr; if (VRMGetNodeMappingData(src_sk).Num()) { src_rig = VRMGetNodeMappingData(src_sk)[0]; } USkeletalMesh *new_sk[2] = {}; USkeleton *new_k[2] = {}; UNodeMappingContainer *new_rig[2] = {}; // 0: humanoid // 1: ue4mannequin for (int i = 0; i < 2; ++i) { FString name_skeleton; FString name_mesh; FString name_rig; if (i == 0) { name_skeleton = (TEXT("SKEL_") + vrmAssetList->BaseFileName + TEXT("_humanoid")); name_mesh = (FString(TEXT("SK_")) + vrmAssetList->BaseFileName + TEXT("_humanoid")); name_rig = (FString(TEXT("RIG_")) + vrmAssetList->BaseFileName + TEXT("_humanoid")); } else { name_skeleton = (TEXT("SKEL_") + vrmAssetList->BaseFileName + TEXT("_ue4mannequin")); name_mesh = (FString(TEXT("SK_")) + vrmAssetList->BaseFileName + TEXT("_ue4mannequin")); name_rig = (FString(TEXT("RIG_")) + vrmAssetList->BaseFileName + TEXT("_ue4mannequin")); } USkeleton *base = nullptr; USkeletalMesh *ss = nullptr; UNodeMappingContainer *rr = nullptr; #if WITH_EDITOR { auto p = vrmAssetList->Package; if (i == 0) { ss = VRM4U_DuplicateObject(src_sk, p, *name_mesh); base = VRM4U_DuplicateObject(src_k, p, *name_skeleton); if (src_rig) { rr = VRM4U_NewObject(p, *name_rig, RF_Public | RF_Standalone); } }else { ss = VRM4U_DuplicateObject(src_sk, p, *name_mesh); base = VRM4U_DuplicateObject(src_k, p, *name_skeleton); if (src_rig) { rr = VRM4U_NewObject(p, *name_rig, RF_Public | RF_Standalone); } } } #else ss = const_cast(src_sk); base = const_cast(src_k); if (i == 1) continue; #endif new_sk[i] = ss; new_k[i] = base; new_rig[i] = rr; VRMSetSkeleton(ss, base); VRMGetNodeMappingData(ss).Empty(); VRMGetNodeMappingData(ss).Add(rr); if (i == 0) { // org -> humanoid renameToHumanoidBone(ss, vrmAssetList->VrmMetaObject, src_sk); } else { // humanoid -> ue4mannequin renameToUE4Bone(ss, vrmAssetList->VrmMetaObject, src_sk); } ss->CalculateInvRefMatrices(); ss->CalculateExtendedBounds(); #if WITH_EDITORONLY_DATA ss->ConvertLegacyLODScreenSize(); #if UE_VERSION_OLDER_THAN(4,20,0) #else ss->UpdateGenerateUpToData(); #endif #endif #if WITH_EDITORONLY_DATA base->SetPreviewMesh(ss); #endif base->RecreateBoneTree(ss); // retarget mode for (int bon = 0; bon < base->GetReferenceSkeleton().GetRawBoneNum(); ++bon) { auto type = src_k->GetBoneTranslationRetargetingMode(bon); base->SetBoneTranslationRetargetingMode(bon, type); } if (i == 0) { vrmAssetList->HumanoidSkeletalMesh = ss; vrmAssetList->VrmHumanoidMetaObject->SkeletalMesh = ss; } else { vrmAssetList->UE4SkeletalMesh = ss; vrmAssetList->VrmMannequinMetaObject->SkeletalMesh = ss; } } // meta rename check // 0: humanoid // 1: ue4mannequin for (int i = 0; i < 2; ++i) { USkeleton* base = nullptr; USkeletalMesh* ss = nullptr; UNodeMappingContainer* rr = nullptr; UVrmMetaObject* meta = nullptr; if (i == 0) { ss = vrmAssetList->HumanoidSkeletalMesh; base = VRMGetSkeleton(ss); meta = vrmAssetList->VrmHumanoidMetaObject; } else { ss = vrmAssetList->UE4SkeletalMesh; base = VRMGetSkeleton(ss); meta = vrmAssetList->VrmMannequinMetaObject; } int boneNo = 0; TPair prev; for (auto& a : meta->humanoidBoneTable) { if (ss==nullptr) break; auto ind = VRMGetRefSkeleton(ss).FindBoneIndex(*(a.Value)); if (boneNo == 0) { prev = a; } if (ind == INDEX_NONE && a.Value!="") { // set prev bone name a.Value = prev.Value; } prev = a; ++boneNo; } } return true; } VrmConvertHumanoid::VrmConvertHumanoid() { } VrmConvertHumanoid::~VrmConvertHumanoid() { }