// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License. #include "VrmConvertRig.h" #include "VrmConvert.h" #include "VrmUtil.h" #include "VrmAssetListObject.h" #include "VrmMetaObject.h" #include "LoaderBPFunctionLibrary.h" #include "VrmBPFunctionLibrary.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/NodeMappingContainer.h" #include "Animation/Skeleton.h" #include "Components/SkeletalMeshComponent.h" #include "UObject/UnrealType.h" #if UE_VERSION_OLDER_THAN(5,4,0) #include "Animation/Rig.h" #endif #include "PhysicsEngine/PhysicsAsset.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #if UE_VERSION_OLDER_THAN(5,5,0) #else #include "PhysicsEngine/SkeletalBodySetup.h" #endif #include "VrmRigHeader.h" #if WITH_EDITOR #include "IPersonaToolkit.h" #include "PersonaModule.h" #include "Modules/ModuleManager.h" #include "Animation/DebugSkelMeshComponent.h" #endif #include #include #include #include #include #include //#include "Engine/.h" namespace { static int GetChildBoneLocal(const FReferenceSkeleton &skeleton, const int32 ParentBoneIndex, TArray & Children) { Children.Reset(); //auto &r = skeleton->GetReferenceSkeleton(); auto &r = skeleton; const int32 NumBones = r.GetRawBoneNum(); for (int32 ChildIndex = ParentBoneIndex + 1; ChildIndex < NumBones; ChildIndex++) { if (ParentBoneIndex == r.GetParentIndex(ChildIndex)) { Children.Add(ChildIndex); } } return Children.Num(); } static bool isSameOrChild(const FReferenceSkeleton &skeleton, const int32 TargetBoneIndex, const int32 SameOrChildBoneIndex) { auto &r = skeleton; if (TargetBoneIndex < 0 || SameOrChildBoneIndex < 0) { return false; } int32 c = SameOrChildBoneIndex; for (int i = 0; i < skeleton.GetRawBoneNum(); ++i) { if (c < 0) break; if (c == TargetBoneIndex) { return true; } c = skeleton.GetParentIndex(c); } return false; } } bool VRMConverter::ConvertRig(UVrmAssetListObject *vrmAssetList) { if (VRMConverter::Options::Get().IsDebugOneBone()) { return true; } if (vrmAssetList->SkeletalMesh == nullptr) { return false; } bool bPlay = false; { bool b1, b2, b3; b1 = b2 = b3 = false; UVrmBPFunctionLibrary::VRMGetPlayMode(b1, b2, b3); bPlay = b1; } if (bPlay) { } // override bone table if (VRMConverter::Options::Get().IsVRMAModel()) { if (vrmAssetList->VrmMetaObject) { for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) { t.Value = t.Key; } } } auto* k = VRMGetSkeleton(vrmAssetList->SkeletalMesh); #if UE_VERSION_OLDER_THAN(4,20,0) #elif UE_VERSION_OLDER_THAN(5,4,0) #if WITH_EDITOR UNodeMappingContainer* mc = nullptr; #if UE_VERSION_OLDER_THAN(5,0,0) { FString name = FString(TEXT("RIG_")) + vrmAssetList->BaseFileName; mc = VRM4U_NewObject(vrmAssetList->Package, *name, RF_Public | RF_Standalone); } VRMGetNodeMappingData(vrmAssetList->SkeletalMesh).Add(mc); URig *EngineHumanoidRig = LoadObject(nullptr, TEXT("/Engine/EngineMeshes/Humanoid.Humanoid"), nullptr, LOAD_None, nullptr); mc->SetSourceAsset(EngineHumanoidRig); VRMGetSkeleton(vrmAssetList->SkeletalMesh)->SetRigConfig(EngineHumanoidRig); mc->SetTargetAsset(vrmAssetList->SkeletalMesh); mc->AddDefaultMapping(); #endif { const VRM::VRMMetadata *meta = reinterpret_cast(aiData->mVRMMeta); auto func = [&](const FString &a, const FString b) { if (mc == nullptr) return; mc->AddMapping(*a, *b); VRMGetSkeleton(vrmAssetList->SkeletalMesh)->SetRigBoneMapping(*a, *b); }; auto func2 = [&](const FString &a, FName b) { func(a, b.ToString()); }; if (meta) { for (auto &t : VRMUtil::table_ue4_vrm) { FString target = t.BoneVRM; const FString &ue4 = t.BoneUE4; if (ue4.Compare(TEXT("Root"), ESearchCase::IgnoreCase) == 0) { auto &a = VRMGetSkeleton(vrmAssetList->SkeletalMesh)->GetReferenceSkeleton().GetRefBoneInfo(); target = a[0].Name.ToString(); } if (target.Len() == 0) { continue; } for (auto b : meta->humanoidBone) { if (target.Compare(b.humanBoneName.C_Str()) != 0) { continue; } target = b.nodeName.C_Str(); break; } func(ue4, target); } { const TArray cc = { TEXT("Root"), TEXT("Pelvis"), TEXT("spine_01"), TEXT("spine_02"), TEXT("spine_03"), TEXT("neck_01"), }; // find bone from child bone for (int i = cc.Num() - 2; i > 0; --i) { if (mc == nullptr) continue; const auto &m = mc->GetNodeMappingTable(); { const auto p0 = m.Find(*cc[i]); if (p0) { // map exist continue; } } const auto p = m.Find(*cc[i + 1]); if (p == nullptr) { // child none continue; } const FName *parentRoot = nullptr; for (int toParent = i - 1; toParent > 0; --toParent) { parentRoot = m.Find(*cc[toParent]); if (parentRoot) { break; } } if (parentRoot == nullptr) { continue; } // find (child) p -> (parent)parentRoot FString newTarget = parentRoot->ToString(); { const int32 index = k->GetReferenceSkeleton().FindBoneIndex(*p); const int32 indexParent = k->GetReferenceSkeleton().GetParentIndex(index); const int32 indexRoot = k->GetReferenceSkeleton().FindBoneIndex(*parentRoot); if (isSameOrChild(k->GetReferenceSkeleton(), indexRoot, indexParent)) { newTarget = k->GetReferenceSkeleton().GetBoneName(indexParent).ToString(); } } func(cc[i], newTarget); } // set null -> parent bone for (int i = 1; i < cc.Num(); ++i) { if (mc == nullptr) continue; const auto &m = mc->GetNodeMappingTable(); { const auto p0 = m.Find(*cc[i]); if (p0) { // map exist continue; } } const auto pp = m.Find(*cc[i-1]); if (pp == nullptr) { // parent none continue; } // map=nullptr, parent=exist FString newTarget = pp->ToString(); { int32 index = k->GetReferenceSkeleton().FindBoneIndex(*pp); TArray child; GetChildBoneLocal(k->GetReferenceSkeleton(), index, child); if (child.Num() == 1) { // use one child // need neck check... //newTarget = k->GetReferenceSkeleton().GetBoneName(child[0]).ToString(); } } func(cc[i], newTarget); } } } else { // BVH auto mapping const auto &rSk = k->GetReferenceSkeleton(); //EngineHumanoidRig->GetSourceReferenceSkeleton(); TArray rBoneList; { for (auto &a : rSk.GetRawRefBoneInfo()) { rBoneList.Add(a.Name.ToString()); } } TArray existTable; int boneIndex = -1; for (auto &b : rBoneList) { ++boneIndex; if (b.Find(TEXT("neck")) >= 0) { const FString t = TEXT("neck_01"); if (existTable.Find(t) >= 0) { continue; } existTable.Add(t); func(t, b); const TArray cc = { TEXT("spine_03"), TEXT("spine_02"), TEXT("spine_01"), TEXT("Pelvis"), TEXT("Root"), }; TArray targetBone; targetBone.SetNum(5); // map const int neckBoneIndex = boneIndex; { int p = neckBoneIndex; for (int i = 0; i < 5; ++i) { int p2 = rSk.GetParentIndex(p); if (p2 >= 0) { p = p2; } targetBone[i] = rSk.GetBoneName(p); } } // upper chest skip for (int i = 0; i < 5; ++i) { if (targetBone[2] == targetBone[3]) { targetBone[2] = targetBone[1]; targetBone[1] = targetBone[0]; } } // root targetBone[cc.Num() - 1] = rSk.GetBoneName(0); // pelvis, spine { int p = neckBoneIndex; for (int i = 0; i < 100; ++i) { p = rSk.GetParentIndex(p); if (p <= 0) break; int p2 = rSk.GetParentIndex(p); if (p2 <= 0) break; // pelvis targetBone[cc.Num() - 2] = rSk.GetBoneName(p2); // spine targetBone[cc.Num() - 3] = rSk.GetBoneName(p); } } //regist for (int i = 0; i < 5; ++i) { func2(cc[i], targetBone[i]); } } if (b.Find(TEXT("head")) >= 0) { const FString t = TEXT("head"); if (existTable.Find(t) >= 0) { continue; } existTable.Add(t); func(t, b); } if (b.Find(TEXT("hand")) >= 0) { if (b.Find("r") >= 0) { const FString t = TEXT("Hand_R"); if (existTable.Find(t) >= 0){ continue; } existTable.Add(t); func(t, b); int p = rSk.GetParentIndex(boneIndex); if (p >= 0) { func2(TEXT("lowerarm_r"), rSk.GetBoneName(p)); } p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("UpperArm_R"), rSk.GetBoneName(p)); p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("clavicle_r"), rSk.GetBoneName(p)); } } { // finger right TArray childBone; GetChildBoneLocal(rSk, boneIndex, childBone); FString tt[6][2] = { {"thumb", "thumb_01_r"}, {"index", "index_01_r"}, {"middle", "middle_01_r"}, {"ring", "ring_01_r"}, {"little", "pinky_01_r"}, // little {"pinky", "pinky_01_r"}, // little }; for (auto& c : childBone) { auto name = rSk.GetBoneName(c); for (int i = 0; i < 6; ++i) { { int tmp = name.ToString().Find(tt[i][0]); if (tmp < 0) continue; } int ind = VRMUtil::ue4_humanoid_bone_list.Find(tt[i][1]); if (ind < 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind], rSk.GetBoneName(c)); TArray tmp = { c }; GetChildBoneLocal(rSk, tmp[0], tmp); if (tmp.Num() <= 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind + 1], rSk.GetBoneName(tmp[0])); GetChildBoneLocal(rSk, tmp[0], tmp); if (tmp.Num() <= 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind + 2], rSk.GetBoneName(tmp[0])); } } } } if (b.Find("l") >= 0) { const FString t = TEXT("Hand_L"); if (existTable.Find(t) >= 0) { continue; } existTable.Add(t); func(t, b); int p = rSk.GetParentIndex(boneIndex); if (p >= 0) { func2(TEXT("lowerarm_l"), rSk.GetBoneName(p)); p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("UpperArm_L"), rSk.GetBoneName(p)); p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("clavicle_l"), rSk.GetBoneName(p)); } } } { // finger left TArray childBone; GetChildBoneLocal(rSk, boneIndex, childBone); FString tt[6][2] = { {"thumb", "thumb_01_l"}, {"index", "index_01_l"}, {"middle", "middle_01_l"}, {"ring", "ring_01_l"}, {"little", "pinky_01_l"}, // little {"pinky", "pinky_01_l"}, // little }; for (auto& c : childBone) { auto name = rSk.GetBoneName(c); for (int i = 0; i < 6; ++i) { { int tmp = name.ToString().Find(tt[i][0]); if (tmp < 0) continue; } int ind = VRMUtil::ue4_humanoid_bone_list.Find(tt[i][1]); if (ind < 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind], rSk.GetBoneName(c)); TArray tmp = { c }; GetChildBoneLocal(rSk, tmp[0], tmp); if (tmp.Num() <= 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind + 1], rSk.GetBoneName(tmp[0])); GetChildBoneLocal(rSk, tmp[0], tmp); if (tmp.Num() <= 0) continue; func2(VRMUtil::ue4_humanoid_bone_list[ind + 2], rSk.GetBoneName(tmp[0])); } } } } } if (b.Find(TEXT("foot")) >= 0) { if (b.Find("r") >= 0) { const FString t = TEXT("Foot_R"); if (existTable.Find(t) >= 0) { continue; } existTable.Add(t); func(t, b); { TArray c; GetChildBoneLocal(rSk, boneIndex, c); if (c.Num()) { func2(TEXT("ball_r"), rSk.GetBoneName(c[0])); } } int p = rSk.GetParentIndex(boneIndex); if (p >= 0) { func2(TEXT("calf_r"), rSk.GetBoneName(p)); p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("Thigh_R"), rSk.GetBoneName(p)); } } } if (b.Find("l") >= 0) { const FString t = TEXT("Foot_L"); if (existTable.Find(t) >= 0) { continue; } existTable.Add(t); func(t, b); { TArray c; GetChildBoneLocal(rSk, boneIndex, c); if (c.Num()) { func2(TEXT("ball_l"), rSk.GetBoneName(c[0])); } } int p = rSk.GetParentIndex(boneIndex); if (p >= 0) { func2(TEXT("calf_l"), rSk.GetBoneName(p)); p = rSk.GetParentIndex(p); if (p >= 0) { func2(TEXT("Thigh_L"), rSk.GetBoneName(p)); } } } } { // pmx bone map for (const auto &t : VRMUtil::table_ue4_pmx) { FString pmxBone; VRMUtil::GetReplacedPMXBone(pmxBone, t.BoneVRM); FString targetList[2] = { pmxBone, t.BoneVRM, }; bool finish = false; for (int i = 0; i < 2; ++i) { FString target = targetList[i];// t.BoneVRM; const FString& ue4 = t.BoneUE4; auto ind = k->GetReferenceSkeleton().FindBoneIndex(*target); if (ind != INDEX_NONE) { func(ue4, target); for (const auto& v : VRMUtil::table_ue4_vrm) { if (v.BoneUE4 == t.BoneUE4) { if (v.BoneVRM.IsEmpty()) { continue; } finish = true; break; } } } if (finish) break; }// 2 loop } }// pmx map } }// map end } //mc->AddMapping if (mc) { mc->PostEditChange(); vrmAssetList->HumanoidRig = mc; } #endif // editor #endif //420 if (VRMConverter::Options::Get().IsVRMAModel()) { // for expression curve for (int i = 0; i < k->GetReferenceSkeleton().GetRawBoneNum(); ++i) { k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Animation); } if (vrmAssetList && vrmAssetList->VrmMetaObject) { for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) { auto i = k->GetReferenceSkeleton().FindBoneIndex(*t.Value); if (i != INDEX_NONE) { if (t.Key.Compare(TEXT("hips"), ESearchCase::IgnoreCase) == 0) { } else { k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Skeleton); } } } } }else{ FString PelvisBoneName; if (vrmAssetList && vrmAssetList->VrmMetaObject) { for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) { if (t.Key.Compare(TEXT("hips"), ESearchCase::IgnoreCase) == 0) { PelvisBoneName = t.Value; break; } } } int bone = -1; for (int i = 0; i < k->GetReferenceSkeleton().GetRawBoneNum(); ++i) { //const int32 BoneIndex = k->GetReferenceSkeleton().FindBoneIndex(InBoneName); k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Skeleton); //FAssetNotifications::SkeletonNeedsToBeSaved(k); if (k->GetReferenceSkeleton().GetBoneName(i).Compare(*PelvisBoneName) == 0) { bone = i; } } bool first = true; while (bone >= 0) { if (first) { k->SetBoneTranslationRetargetingMode(bone, EBoneTranslationRetargetingMode::AnimationScaled); } else { k->SetBoneTranslationRetargetingMode(bone, EBoneTranslationRetargetingMode::Animation); } first = false; bone = k->GetReferenceSkeleton().GetParentIndex(bone); } { FName n[] = { TEXT("ik_foot_root"), TEXT("ik_foot_l"), TEXT("ik_foot_r"), TEXT("ik_hand_root"), TEXT("ik_hand_gun"), TEXT("ik_hand_l"), TEXT("ik_hand_r"), }; for (auto& s : n) { int32 ind = k->GetReferenceSkeleton().FindBoneIndex(s); if (ind < 0) continue; k->SetBoneTranslationRetargetingMode(ind, EBoneTranslationRetargetingMode::Animation, false); } } if (VRMConverter::Options::Get().IsPMXModel()) { // center to animscale int32 ind = k->GetReferenceSkeleton().FindBoneIndex(*(VRMUtil::table_ue4_pmx[1].BoneVRM)); if (ind >= 0) { k->SetBoneTranslationRetargetingMode(ind, EBoneTranslationRetargetingMode::AnimationScaled, false); } } if (VRMConverter::Options::Get().IsBVHModel()) { // 0 == root by vrm4u dummy // 1 == root by model k->SetBoneTranslationRetargetingMode(0, EBoneTranslationRetargetingMode::Animation, false); k->SetBoneTranslationRetargetingMode(1, EBoneTranslationRetargetingMode::Animation, false); } if (VRMConverter::Options::Get().IsVRMModel() == false) { k->SetBoneTranslationRetargetingMode(0, EBoneTranslationRetargetingMode::Animation, false); } } // dummy Collision if (vrmAssetList) { const VRM::VRMMetadata* meta = reinterpret_cast(aiData->mVRMMeta); USkeletalMesh* sk = vrmAssetList->SkeletalMesh; UPhysicsAsset* pa = VRMGetPhysicsAsset(sk); const FString dummy_target[] = { TEXT("hips"), TEXT("head"), TEXT("rightHand"), TEXT("leftHand"), TEXT("leftMiddleDistal"), TEXT("rightMiddleDistal"), TEXT("rightFoot"), TEXT("leftFoot"), TEXT("leftToes"), TEXT("rightToes"), TEXT("rightLowerArm"), TEXT("leftLowerArm"), TEXT("rightLowerLeg"), TEXT("leftLowerLeg"), }; if (meta && pa) { for (const auto& a : meta->humanoidBone) { { bool bFound = false; for (auto& d : dummy_target) { if (d.Compare(a.humanBoneName.C_Str()) == 0) { bFound = true; } } if (bFound == false) { continue; } } { bool b = false; for (const auto& bs : pa->SkeletalBodySetups) { FString s = bs->BoneName.ToString(); if (s.Compare(a.nodeName.C_Str(), ESearchCase::IgnoreCase) == 0) { b = true; break; } } if (b) { continue; } } const int targetBone = VRMGetRefSkeleton(sk).FindRawBoneIndex(a.nodeName.C_Str()); if (targetBone == INDEX_NONE) { break; } USkeletalBodySetup* bs = nullptr; int BodyIndex1 = -1; bs = NewObject(pa, *(FString(TEXT("dummy_for_clip")) + a.humanBoneName.C_Str()), RF_Transactional); FKAggregateGeom agg; FKSphereElem SphereElem; SphereElem.Center = FVector(0); SphereElem.Radius = 1.f;// center.Size();// 1.f; agg.SphereElems.Add(SphereElem); SphereElem.SetName(TEXT("dummy_for_clip")); bs->Modify(); bs->BoneName = a.nodeName.C_Str(); bs->AddCollisionFrom(agg); bs->CollisionTraceFlag = CTF_UseSimpleAsComplex; // newly created bodies default to simulating bs->PhysicsType = PhysType_Kinematic; // fix //bs->get bs->CollisionReponse = EBodyCollisionResponse::BodyCollision_Disabled; bs->DefaultInstance.InertiaTensorScale.Set(2, 2, 2); bs->DefaultInstance.LinearDamping = 0.f; bs->DefaultInstance.AngularDamping = 0.f; bs->InvalidatePhysicsData(); bs->CreatePhysicsMeshes(); BodyIndex1 = pa->SkeletalBodySetups.Add(bs); //break; } pa->UpdateBoundsBodiesArray(); pa->UpdateBodySetupIndexMap(); RefreshSkelMeshOnPhysicsAssetChange(sk); #if WITH_EDITOR pa->RefreshPhysicsAssetChange(); #endif #if WITH_EDITOR if (VRMConverter::IsImportMode()) { pa->PostEditChange(); } #endif } }// collistion return true; }