Files
FunnyGirl/Plugins/VRM4U/Source/VRM4ULoader/Private/VrmSkeleton.cpp
T
2026-05-19 13:42:41 +07:00

662 lines
17 KiB
C++

// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmSkeleton.h"
#include "VrmAssetListObject.h"
#include "VrmMetaObject.h"
#include "Engine/SkeletalMesh.h"
//#include "UnStringConv.h
#include "VrmConvert.h"
#include "VrmUtil.h"
#include "VRM4ULoaderLog.h"
#include <assimp/scene.h> // Output data structure
#include <assimp/mesh.h> // Output data structure
static int countParent(const aiNode *node, TArray<const aiNode*> &t, int c) {
for (auto &a : t) {
for (uint32_t i = 0; i < a->mNumChildren; ++i) {
if (node == a->mChildren[i]) {
return countParent(a, t, c + 1);
}
}
}
return c;
}
static int countChild(aiNode *node, int c) {
c += node->mNumChildren;
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
c = countChild(node->mChildren[i], c);
}
return c;
}
static bool findActiveBone(const aiNode *node, TArray<FString> &table) {
if (table.Find(node->mName.C_Str()) != INDEX_NONE) {
return true;
}
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
if (findActiveBone(node->mChildren[i], table)) {
return true;
}
}
return false;
}
static TArray<FString> makeActiveBone(const aiScene *scene) {
TArray <FString> boneNameTable;
for (uint32 m = 0; m < scene->mNumMeshes; ++m) {
const auto &aiM = *scene->mMeshes[m];
for (uint32 b = 0; b < aiM.mNumBones; ++b) {
const auto &aiB = *aiM.mBones[b];
boneNameTable.AddUnique(aiB.mName.C_Str());
}
}
return boneNameTable;
}
static TMap<const aiNode*, const aiBone* > makeAiBoneTable(const aiScene* scene, TArray<const aiNode*> &nodeArray) {
TMap<const aiNode*, const aiBone*> table;
for (uint32 m = 0; m < scene->mNumMeshes; ++m) {
const auto& aiM = *scene->mMeshes[m];
for (uint32 b = 0; b < aiM.mNumBones; ++b) {
const auto& aiB = aiM.mBones[b];
if (nodeArray.Find(aiB->mNode)){
table.Add(aiB->mNode, aiB);
//break;
}
}
}
return table;
}
static TMap<const aiNode*, const aiSkeletonBone* > makeAiSkeletonBoneTable(const aiScene* scene, TArray<const aiNode*>& nodeArray) {
TMap<const aiNode*, const aiSkeletonBone*> table;
for (uint32 m = 0; m < scene->mNumSkeletons; ++m) {
const auto& aiS = *scene->mSkeletons[m];
for (uint32 b = 0; b < aiS.mNumBones; ++b) {
const auto& aiB = aiS.mBones[b];
if (nodeArray.Find(aiB->mNode)) {
table.Add(aiB->mNode, aiB);
//break;
}
}
}
return table;
}
static FMatrix convertAiMatToFMatrix(aiMatrix4x4 t, bool bOffsetMode = false) {
FMatrix m;
m.M[0][0] = t.a1; m.M[1][0] = t.a2; m.M[2][0] = t.a3; m.M[3][0] = -t.a4 * 100.f;
m.M[0][1] = t.b1; m.M[1][1] = t.b2; m.M[2][1] = t.b3; m.M[3][1] = t.c4 * 100.f;//t.b4*100.f;
m.M[0][2] = t.c1; m.M[1][2] = t.c2; m.M[2][2] = t.c3; m.M[3][2] = t.b4 * 100.f;//t.c4*100.f;
m.M[0][3] = t.d1; m.M[1][3] = t.d2; m.M[2][3] = t.d3; m.M[3][3] = t.d4;
if (VRMConverter::Options::Get().IsVRM10Model()) {
m.M[0][0] = t.a1; m.M[1][0] = t.a2; m.M[2][0] = t.a3; m.M[3][0] = t.a4 * 100.f;
m.M[0][1] = t.b1; m.M[1][1] = t.b2; m.M[2][1] = t.b3; m.M[3][1] = t.b4 * 100.f;//t.b4*100.f;
m.M[0][2] = t.c1; m.M[1][2] = t.c2; m.M[2][2] = t.c3; m.M[3][2] = t.c4 * 100.f;//t.c4*100.f;
m.M[0][3] = t.d1; m.M[1][3] = t.d2; m.M[2][3] = t.d3; m.M[3][3] = t.d4;
// rot
{
FTransform f;
if (bOffsetMode) {
//f.SetRotation(FRotator(0, 0, 180).Quaternion());
f.SetRotation(FRotator(0, 0, 90).Quaternion());
} else {
f.SetRotation(FRotator(0, 0, 90).Quaternion());
}
m = f.ToMatrixNoScale() * m * f.ToMatrixNoScale().Inverse();
}
}
if (VRMConverter::Options::Get().IsPMXModel() || VRMConverter::Options::Get().IsBVHModel()) {
m.M[3][0] *= -1.f;
m.M[3][1] *= -1.f;
}
{
m.M[3][0] *= VRMConverter::Options::Get().GetModelScale();
m.M[3][1] *= VRMConverter::Options::Get().GetModelScale();
m.M[3][2] *= VRMConverter::Options::Get().GetModelScale();
//m.M[3][3] *= VRMConverter::Options::Get().GetModelScale();
}
return m;
}
static bool hasMeshInChild(aiNode *node) {
if (node == nullptr) {
return false;
}
if (node->mNumMeshes > 0) {
return true;
}
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
bool b = hasMeshInChild(node->mChildren[i]);
if (b) {
return true;
}
}
return false;
}
static void rr3(const aiNode *node, TArray<const aiNode*> &t, bool &bHasMesh, const bool bOnlyRootBone) {
bHasMesh = false;
if (node == nullptr) {
return;
}
if (node->mNumMeshes > 0) {
bHasMesh = true;
}
t.Push(node);
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
bool b = false;
rr3(node->mChildren[i], t, b, bOnlyRootBone);
if (b) {
bHasMesh = true;
}
}
if (bHasMesh==false && bOnlyRootBone) {
//t.Remove(node);
}
}
static void rr(const aiNode *node, TArray<const aiNode*> &t, bool &bHasMesh, const bool bOnlyRootBone, aiScene *scene) {
auto target = node;
if (bOnlyRootBone) {
int maxIndex = -1;
int maxChild = 0;
// find deep tree
for (uint32 i = 0; i < node->mNumChildren; ++i) {
int cc = countChild(node->mChildren[i], 0);
if (cc > maxChild) {
maxChild = cc;
maxIndex = i;
}
}
auto tableBone = makeActiveBone(scene);
for (uint32 i = 0; i < node->mNumChildren; ++i) {
if (i == maxIndex) {
continue;
}
if (findActiveBone(node->mChildren[i], tableBone)){
maxIndex = -1;
break;
}
}
if (maxIndex >= 0) {
const auto trans = node->mChildren[maxIndex]->mTransformation;
float f = 0.f;
f += FMath::Abs(trans.a4) + FMath::Abs(trans.b4) + FMath::Abs(trans.c4);
if (f < 1.e-8f) {
target = node->mChildren[maxIndex];
} else {
// root transform must zero.
}
}
}
rr3(target, t, bHasMesh, bOnlyRootBone);
}
/////////////////////////
void VRMSkeleton::readVrmBone(aiScene* scene, int& boneOffset, FReferenceSkeleton &RefSkeleton, UVrmAssetListObject* vrmAssetList) {
boneOffset = 0;
FReferenceSkeletonModifier RefSkelModifier(RefSkeleton, nullptr);
{
FTransform t;
t.SetIdentity();
for (int i = 0; i < RefSkeleton.GetRawBoneNum(); ++i) {
RefSkelModifier.UpdateRefPoseTransform(i, t);
}
}
TArray<const aiNode*> nodeArray;
{
bool dummy = false;
bool bSimpleRootBone = false;
bSimpleRootBone = VRMConverter::Options::Get().IsSimpleRootBone();
rr(scene->mRootNode, nodeArray, dummy, bSimpleRootBone, scene);
}
{
// rename bone
TMap<FString, int> count;
for (int i = 0; i < nodeArray.Num(); ++i) {
auto& node = nodeArray[i];
FString str = UTF8_TO_TCHAR(node->mName.C_Str());
if (count.Contains(str)) {
count[str]++;
} else {
count.Add(str, 1);
}
}
for (int i = nodeArray.Num() - 1; i >= 0; --i) {
aiNode* node = const_cast<aiNode*>(nodeArray[i]);
FString str = UTF8_TO_TCHAR(node->mName.C_Str());
if (count.Contains(str) == false) continue;
auto c = count[str];
if (c >= 2) {
char tmp[512];
snprintf(tmp, 512, "%s_%02d", node->mName.C_Str(), c);
node->mName = tmp;
count[str]--;
}
}
}
TMap<const aiNode*, const aiBone*> aiBoneTable = makeAiBoneTable(scene, nodeArray);
//TMap<const aiNode*, const aiSkeletonBone*> aiBoneTable = makeAiSkeletonBoneTable(scene, nodeArray);
{
TArray<const aiBone*> v;
aiBoneTable.GenerateValueArray(v);
for (int i = 2; i < v.Num(); ++i) {
if (v[i - 1]->mArmature != v[i]->mArmature) {
// use armature
break;
}
}
}
{
int totalBoneCount = 0;
TArray<FTransform> poseGlobal_bindpose; // bone
TArray<FTransform> poseGlobal_tpose; // node
TArray<FTransform> poseGlobal_tpose_rootIdentity; // node rootIdentity
TArray<FTransform> poseLocal_bindpose; // bone
TArray<FTransform> poseLocal_tpose; // node
TArray<FTransform> poseLocal_tpose_rootIdentity; // node rootIdentity
FTransform tpose_root;
poseGlobal_bindpose.SetNum(nodeArray.Num());
poseGlobal_tpose.SetNum(nodeArray.Num());
poseGlobal_tpose_rootIdentity.SetNum(nodeArray.Num());
poseLocal_bindpose.SetNum(nodeArray.Num());
poseLocal_tpose.SetNum(nodeArray.Num());
poseLocal_tpose_rootIdentity.SetNum(nodeArray.Num());
// generate transform
{
// t-pose
for (int nodeNo = 0; nodeNo < nodeArray.Num(); ++nodeNo) {
auto node = nodeArray[nodeNo];
FString nodeName = UTF8_TO_TCHAR(node->mName.C_Str());
int32 ParentIndex = INDEX_NONE;
if (nodeArray.Find(node->mParent, ParentIndex) == false) {
ParentIndex = INDEX_NONE;
}
// t-pose (node pose)
{
FMatrix m = convertAiMatToFMatrix(node->mTransformation);
FTransform localpose;
localpose.SetFromMatrix(m);
FTransform localpose_Identity = localpose;
if (VRMConverter::Options::Get().IsRemoveRootBoneRotation()) {
// localpose correct
if (ParentIndex >= 0) {
localpose_Identity = tpose_root.Inverse() * localpose * tpose_root;
} else {
tpose_root = localpose;
localpose_Identity.SetIdentity();
if (VRMConverter::Options::Get().IsRemoveRootBonePosition() == false) {
localpose_Identity.SetTranslation(localpose.GetTranslation());
}
}
}// identity set
// pose
if (ParentIndex >= 0) {
poseGlobal_tpose[nodeNo] = localpose * poseGlobal_tpose[ParentIndex];
poseGlobal_tpose_rootIdentity[nodeNo] = localpose_Identity * poseGlobal_tpose_rootIdentity[ParentIndex];
} else {
poseGlobal_tpose[nodeNo] = localpose;
poseGlobal_tpose_rootIdentity[nodeNo] = localpose_Identity;
}
poseLocal_tpose[nodeNo] = localpose;
poseLocal_tpose_rootIdentity[nodeNo] = localpose_Identity;
}
}// tpose
}
for (int nodeNo = 0; nodeNo < nodeArray.Num(); ++nodeNo) {
auto node = nodeArray[nodeNo];
FString nodeName = UTF8_TO_TCHAR(node->mName.C_Str());
int32 ParentIndex = INDEX_NONE;
if (nodeArray.Find(node->mParent, ParentIndex) == false) {
ParentIndex = INDEX_NONE;
}
// bind-pose
{
auto pBone = aiBoneTable.Find(node);
if (pBone == nullptr) {
// generate from tpose matrix
if (ParentIndex == INDEX_NONE) {
poseLocal_bindpose[nodeNo] = poseLocal_tpose_rootIdentity[nodeNo];
poseGlobal_bindpose[nodeNo] = poseLocal_bindpose[nodeNo];
} else {
poseLocal_bindpose[nodeNo] = poseLocal_tpose_rootIdentity[nodeNo];
poseGlobal_bindpose[nodeNo] = poseLocal_bindpose[nodeNo] * poseGlobal_bindpose[ParentIndex];
}
}else{
auto bone = *pBone;
FMatrix m = convertAiMatToFMatrix(bone->mOffsetMatrix, true);
if (ParentIndex == INDEX_NONE) {
} else {
}
FTransform globalpose;
globalpose.SetFromMatrix(m.Inverse());
poseGlobal_bindpose[nodeNo] = globalpose;
if (ParentIndex == INDEX_NONE) {
poseLocal_bindpose[nodeNo] = globalpose;
}else{
poseLocal_bindpose[nodeNo] = globalpose * poseGlobal_bindpose[ParentIndex].Inverse();
}
}
}
} // bind pose
for (int nodeNo = 0; nodeNo < nodeArray.Num(); ++nodeNo) {
auto node = nodeArray[nodeNo];
FMeshBoneInfo info;
info.Name = UTF8_TO_TCHAR(node->mName.C_Str());
#if WITH_EDITORONLY_DATA
info.ExportName = UTF8_TO_TCHAR(node->mName.C_Str());
#endif
int32 ParentIndexByNode = INDEX_NONE;
if (nodeArray.Find(node->mParent, ParentIndexByNode) == false) {
ParentIndexByNode = INDEX_NONE;
if (VRMConverter::Options::Get().IsBVHModel()) {
// ダミーのRoot骨を追加する。BVHはRoot骨にTransが入っていることがある。
// Transがあると、リターゲットがうまくできない
FMeshBoneInfo inf;
inf.Name = TEXT("root_dummy");
inf.ParentIndex = INDEX_NONE;
RefSkelModifier.Add(inf, FTransform());
}
}
if (VRMConverter::Options::Get().IsBVHModel()) {
info.ParentIndex = ParentIndexByNode + 1;
} else {
info.ParentIndex = ParentIndexByNode;
}
FMatrix m = convertAiMatToFMatrix(node->mTransformation);
FTransform pose;
if (VRMConverter::Options::Get().IsVRM10Bindpose()) {
pose = poseLocal_bindpose[nodeNo];
}else{
pose = poseLocal_tpose_rootIdentity[nodeNo];
}
// remove local axis
if (VRMConverter::Options::Get().IsVRM10Model()) {
if (VRMConverter::Options::Get().IsVRM10RemoveLocalRotation()) {
if (vrmAssetList){
if (vrmAssetList->VrmMetaObject) {
TArray<FString> v;
vrmAssetList->VrmMetaObject->humanoidBoneTable.GenerateValueArray(v);
//if (v.Find(info.Name.ToString()) != INDEX_NONE) {
pose.SetRotation(FQuat::Identity);
if (ParentIndexByNode >= 0) {
if (VRMConverter::Options::Get().IsVRM10Bindpose()) {
pose.SetTranslation(poseGlobal_bindpose[nodeNo].GetLocation() - poseGlobal_bindpose[ParentIndexByNode].GetLocation());
} else {
pose.SetTranslation(poseGlobal_tpose[nodeNo].GetLocation() - poseGlobal_tpose[ParentIndexByNode].GetLocation());
}
}
//}
}
}
}
}
// bone name check
{
auto baseName = info.Name.ToString();
for (int c = 0; c < 100; ++c) {
if (RefSkeleton.FindRawBoneIndex(info.Name) == INDEX_NONE) {
break;
}
info.Name = *FString::Printf(TEXT("%s_vrm4u%02d"), *baseName, c);
}
}
if (totalBoneCount > 0 && ParentIndexByNode == INDEX_NONE) {
// bad bone. root?
continue;
}
if (VRMConverter::Options::Get().IsVRMAModel()) {
if (vrmAssetList->VrmMetaObject) {
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
if (t.Value == info.Name.ToString()) {
info.Name = *t.Key;
break;
}
}
}
}
RefSkelModifier.Add(info, pose);
totalBoneCount++;
if (vrmAssetList) {
vrmAssetList->Pose_bind.Add(UTF8_TO_TCHAR(node->mName.C_Str()), poseGlobal_bindpose[nodeNo]);
vrmAssetList->Pose_tpose.Add(UTF8_TO_TCHAR(node->mName.C_Str()), poseGlobal_tpose[nodeNo]);
vrmAssetList->model_root_transform = tpose_root;
}
if (totalBoneCount == 1) {
if (VRMConverter::Options::Get().IsDebugOneBone()) {
// root only
break;
}
}
}
}
}
void VRMSkeleton::addIKBone(UVrmAssetListObject* vrmAssetList, USkeletalMesh *sk) {
if (VRMConverter::Options::Get().IsGenerateIKBone() == false) {
return;
}
// refskeleton
auto& BonePose = VRMGetRefSkeleton(sk).GetRefBonePose();
FReferenceSkeletonModifier RefSkelModifier(VRMGetRefSkeleton(sk), VRMGetSkeleton(sk));
{
FString BoneCheckList[] = {
TEXT("ik_foot_l"),
TEXT("ik_foot_r"),
TEXT("ik_hand_l"),
TEXT("ik_hand_r"),
TEXT("ik_hand_root"),
TEXT("ik_foot_root"),
TEXT("ik_hand_gun"),
};
for (auto& s : BoneCheckList) {
if (RefSkelModifier.FindBoneIndex(*s) != INDEX_NONE) {
return;
}
}
}
FTransform trans;
FMeshBoneInfo info;
info.ParentIndex = 0;
info.Name = TEXT("ik_foot_root");
RefSkelModifier.Add(info, trans);
info.Name = TEXT("ik_hand_root");
RefSkelModifier.Add(info, trans);
// rebuild tmp
VRMGetRefSkeleton(sk).RebuildRefSkeleton(VRMGetSkeleton(sk), true);
{
info.ParentIndex = RefSkelModifier.FindBoneIndex(TEXT("ik_foot_root"));
info.Name = ("ik_foot_l");
RefSkelModifier.Add(info, trans);
info.Name = ("ik_foot_r");
RefSkelModifier.Add(info, trans);
info.ParentIndex = RefSkelModifier.FindBoneIndex(TEXT("ik_hand_root"));
info.Name = ("ik_hand_gun");
RefSkelModifier.Add(info, trans);
}
// rebuild tmp
VRMGetRefSkeleton(sk).RebuildRefSkeleton(VRMGetSkeleton(sk), true);
{
info.ParentIndex = RefSkelModifier.FindBoneIndex(TEXT("ik_hand_gun"));
info.Name = ("ik_hand_l");
RefSkelModifier.Add(info, trans);
info.Name = ("ik_hand_r");
RefSkelModifier.Add(info, trans);
}
// rebuild tmp
VRMGetRefSkeleton(sk).RebuildRefSkeleton(VRMGetSkeleton(sk), true);
{
FString orgBone[4];
FString newBone[4] = {
TEXT("ik_foot_l"),
TEXT("ik_foot_r"),
TEXT("ik_hand_l"),
TEXT("ik_hand_r"),
};
FString parentBone[4] = {
TEXT("ik_foot_root"),
TEXT("ik_foot_root"),
TEXT("ik_hand_gun"),
TEXT("ik_hand_gun"),
};
{
FString ue4bone[4] = {
TEXT("foot_l"),
TEXT("foot_r"),
TEXT("hand_l"),
TEXT("hand_r"),
};
for (int i = 0; i < 4; ++i) {
FString humanoid;
for (auto& t : VRMUtil::table_ue4_vrm) {
if (t.BoneUE4 == ue4bone[i]) {
humanoid = t.BoneVRM;
break;
}
}
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
if (t.Key == humanoid) {
orgBone[i] = t.Value;
break;
}
}
}
}
// gun only
FTransform gun_trans;
{
{
const int i = 3;
int32 ind = 0;
ind = VRMGetRefSkeleton(sk).FindBoneIndex(*orgBone[i]);
if (ind >= 0) {
FTransform a;
while (ind >= 0) {
a *= BonePose[ind];
ind = VRMGetRefSkeleton(sk).GetParentIndex(ind);
}
gun_trans = a;
RefSkelModifier.UpdateRefPoseTransform(RefSkelModifier.FindBoneIndex(TEXT("ik_hand_gun")), a);
}
}
// rebuild tmp
VRMGetRefSkeleton(sk).RebuildRefSkeleton(VRMGetSkeleton(sk), true);
}
for (int i = 0; i < 4; ++i) {
int32 ind = 0;
ind = RefSkelModifier.FindBoneIndex(*orgBone[i]);
if (ind >= 0) {
bool first = true;
FTransform a;
while (ind >= 0) {
a *= BonePose[ind];
ind = VRMGetRefSkeleton(sk).GetParentIndex(ind);
}
if (i >= 2) {
a = gun_trans.Inverse() * a;
}
RefSkelModifier.UpdateRefPoseTransform(RefSkelModifier.FindBoneIndex(*newBone[i]), a);
}
}
}
}