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

2750 lines
78 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
// The code associated with the initialization of the mesh is based on the Engine source.
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvertModel.h"
#include "VrmConvert.h"
#include "VrmAssetListObject.h"
#include "VrmMetaObject.h"
#include "VrmSkeleton.h"
#include "LoaderBPFunctionLibrary.h"
#include "VRM4ULoaderLog.h"
#if UE_VERSION_OLDER_THAN(5,1,0)
#else
#include "VrmAssetUserData.h"
#endif
#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 "Rendering/SkinWeightVertexBuffer.h"
#include "CommonFrameRates.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
#include "Internationalization/Internationalization.h"
#if UE_VERSION_OLDER_THAN(5,5,0)
#else
#include "PhysicsEngine/SkeletalBodySetup.h"
#endif
#include "Animation/AnimSequence.h"
#include "Animation/AnimBlueprint.h"
#include "Async/ParallelFor.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/postprocess.h>
#include <assimp/GltfMaterial.h>
#include <assimp/vrm/vrmmeta.h>
#if WITH_EDITOR
#include "Kismet2/KismetEditorUtilities.h"
#endif
#define LOCTEXT_NAMESPACE "VRM4U"
#if UE_VERSION_OLDER_THAN(5,2,0)
typedef uint8 VRM4U_BONE_INFLUENCE_TYPE;
static constexpr int VRM4U_MaxBoneWeight = 255;
static constexpr float VRM4U_MaxBoneWeightFloat = 255.f;
#else
#include "BoneWeights.h"
typedef uint16 VRM4U_BONE_INFLUENCE_TYPE;
static constexpr int VRM4U_MaxBoneWeight = UE::AnimationCore::MaxRawBoneWeight;
static constexpr float VRM4U_MaxBoneWeightFloat = UE::AnimationCore::MaxRawBoneWeightFloat;
#endif
static constexpr float VRM4U_InvMaxRawBoneWeightFloat = 1.0f / VRM4U_MaxBoneWeightFloat;
static constexpr float VRM4U_BoneWeightThreshold = VRM4U_InvMaxRawBoneWeightFloat;
#if WITH_EDITOR
typedef FSoftSkinVertex FSoftSkinVertexLocal;
#else
namespace {
struct FSoftSkinVertexLocal
{
#if UE_VERSION_OLDER_THAN(5,0,0)
FVector Position;
// Tangent, U-direction
FVector TangentX;
// Binormal, V-direction
FVector TangentY;
// Normal
FVector4 TangentZ;
#else
FVector3f Position;
FVector3f TangentX;
FVector3f TangentY;
FVector4f TangentZ;
#endif
// UVs
#if UE_VERSION_OLDER_THAN(5,0,0)
FVector2D UVs[MAX_TEXCOORDS];
#else
FVector2f UVs[MAX_TEXCOORDS];
#endif
// VertexColor
FColor Color;
FBoneIndexType InfluenceBones[MAX_TOTAL_INFLUENCES];
VRM4U_BONE_INFLUENCE_TYPE InfluenceWeights[MAX_TOTAL_INFLUENCES];
/** If this vert is rigidly weighted to a bone, return true and the bone index. Otherwise return false. */
//ENGINE_API bool GetRigidWeightBone(uint8& OutBoneIndex) const;
/** Returns the maximum weight of any bone that influences this vertex. */
//ENGINE_API uint8 GetMaximumWeight() const;
/**
* Serializer
*
* @param Ar - archive to serialize with
* @param V - vertex to serialize
* @return archive that was used
*/
//friend FArchive& operator<<(FArchive& Ar, FSoftSkinVertex& V);
};
}
#endif
namespace {
struct BoneMapOpt {
int boneIndex;
float weight;
BoneMapOpt() : boneIndex(0), weight(0.f) {}
bool operator<(const BoneMapOpt &b) const{
return weight < b.weight;
}
};
TArray<FString> addedList;
}
static const aiNode* GetBoneNodeFromMeshID(const int &meshID, const aiNode *node) {
if (node == nullptr) {
return nullptr;
}
for (uint32_t i = 0; i < node->mNumMeshes; ++i) {
if (node->mMeshes[i] == meshID) {
return node;
}
}
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
auto a = GetBoneNodeFromMeshID(meshID, node->mChildren[i]);
if (a) {
return a;
}
}
return nullptr;
}
static const aiNode* GetNodeFromMeshID(int meshID, const aiScene *aiData) {
return GetBoneNodeFromMeshID(meshID, aiData->mRootNode);
}
static int GetChildBoneLocal(const USkeleton *skeleton, const int32 ParentBoneIndex, TArray<int32> & Children) {
Children.Reset();
auto &r = skeleton->GetReferenceSkeleton();
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 void FindMeshInfo(const aiScene* scene, aiNode* node, FReturnedData& result, UVrmAssetListObject *vrmAssetList)
{
if (VRMConverter::Options::Get().IsDebugNoMesh()) {
return;
}
for (uint32 MeshNo = 0; MeshNo < node->mNumMeshes; MeshNo++)
{
FString Fs = UTF8_TO_TCHAR(node->mName.C_Str());
int meshidx = node->mMeshes[MeshNo];
aiMesh *mesh = scene->mMeshes[meshidx];
FMeshInfo_VRM4U &mi = result.meshInfo[meshidx];
//result.meshToIndex.FindOrAdd(mesh) = mi.Vertices.Num();
bool bSkin = true;
if (mesh->mNumBones == 0) {
bSkin = false;
}
if (0) {
int m = mesh->mNumVertices;
mi.Vertices.Reserve(m);
mi.Normals.Reserve(m);
if (mesh->HasVertexColors(0)) {
mi.VertexColors.Reserve(m);
}
if (mesh->HasTangentsAndBitangents()) {
mi.Tangents.Reserve(m);
}
if (mesh->HasTextureCoords(0)) {
mi.UV0.Reserve(m);
}
}
//transform.
aiMatrix4x4 tempTrans = node->mTransformation;
{
auto *p = node->mParent;
if (p == nullptr) {
// 原点オフセットぶんは ここでは考慮しない。頂点が2重に変換されてしまうため
tempTrans = aiMatrix4x4();
}
while (p) {
if (p->mParent == nullptr) {
// 原点オフセットぶん。たどるのはここまで。
break;
}
aiMatrix4x4 aiMat = p->mTransformation;
tempTrans *= aiMat;
p = p->mParent;
}
}
FMatrix tempMatrix;
tempMatrix.M[0][0] = tempTrans.a1; tempMatrix.M[0][1] = tempTrans.b1; tempMatrix.M[0][2] = tempTrans.c1; tempMatrix.M[0][3] = tempTrans.d1;
tempMatrix.M[1][0] = tempTrans.a2; tempMatrix.M[1][1] = tempTrans.b2; tempMatrix.M[1][2] = tempTrans.c2; tempMatrix.M[1][3] = tempTrans.d2;
tempMatrix.M[2][0] = tempTrans.a3; tempMatrix.M[2][1] = tempTrans.b3; tempMatrix.M[2][2] = tempTrans.c3; tempMatrix.M[2][3] = tempTrans.d3;
tempMatrix.M[3][0] = tempTrans.a4; tempMatrix.M[3][1] = tempTrans.b4; tempMatrix.M[3][2] = tempTrans.c4; tempMatrix.M[3][3] = tempTrans.d4;
mi.RelativeTransform = FTransform(tempMatrix);
auto &useFlag = mi.vertexUseFlag;
if (VRMConverter::Options::Get().IsOptimizeVertex()) {
// no morphtarget
// optimize vertex
// use flag
useFlag.AddZeroed(mesh->mNumVertices);
for (uint32_t f = 0; f < mesh->mNumFaces; ++f) {
auto &face = mesh->mFaces[f];
for (uint32_t d = 0; d < face.mNumIndices; ++d) {
auto &ind = face.mIndices[d];
useFlag[ind] = true;
}
}
bool hasSkipVertex = false;
// optimize table
TArray<uint32_t> useTable;
useTable.AddZeroed(useFlag.Num());
{
int c = 0;
for (int t = 0; t < useFlag.Num(); ++t) {
useTable[t] = t - c;
if (useFlag[t] == false) {
++c;
hasSkipVertex = true;
}
}
}
if (hasSkipVertex) {
// replace face index
for (uint32_t f = 0; f < mesh->mNumFaces; ++f) {
auto& face = mesh->mFaces[f];
for (uint32_t d = 0; d < face.mNumIndices; ++d) {
auto& ind = face.mIndices[d];
face.mIndices[d] = useTable[face.mIndices[d]];
}
}
// replace weight index
for (uint32_t b = 0; b < mesh->mNumBones; ++b) {
auto& bone = mesh->mBones[b];
for (uint32_t w = 0; w < bone->mNumWeights; ++w) {
auto& weight = bone->mWeights[w];
uint32_t ind = weight.mVertexId;
weight.mVertexId = useTable[ind];
if (useFlag[ind] == 0) {
weight.mWeight = 0.f;
}
}
}
}
}
if (useFlag.Num() > 0) {
mi.vertexIndexOptTable.SetNumZeroed(useFlag.Num());
}
mi.useVertexCount = 0;
//vet
for (uint32 j = 0; j < mesh->mNumVertices; ++j)
{
if ((int)j < useFlag.Num()) {
if (useFlag[j] == false) {
continue;
}
mi.vertexIndexOptTable[j] = mi.useVertexCount;
mi.useVertexCount++;
}
FVector vertex = FVector(
mesh->mVertices[j].x,
mesh->mVertices[j].y,
mesh->mVertices[j].z);
vertex = mi.RelativeTransform.TransformFVector4(vertex);
mi.Vertices.Push(vertex);
//Normal
if (mesh->HasNormals())
{
FVector normal = FVector(
mesh->mNormals[j].x,
mesh->mNormals[j].y,
mesh->mNormals[j].z);
//normal = mi.RelativeTransform.TransformFVector4(normal);
mi.Normals.Push(normal);
} else
{
mi.Normals.Push(FVector(0, 1, 0));
}
//UV Coordinates - inconsistent coordinates
for (int u = 0; u < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++u) {
if (mesh->HasTextureCoords(u))
{
if (mi.UV0.Num() <= u) {
mi.UV0.Push(TArray<FVector2D>());
if (u >= 1) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("test uv2.\n"));
}
}
FVector2D uv = FVector2D(mesh->mTextureCoords[u][j].x, 1.0-mesh->mTextureCoords[u][j].y);
mi.UV0[u].Add(uv);
}
}
//Tangent
if (mesh->HasTangentsAndBitangents())
{
FVector v(mesh->mTangents[j].x, mesh->mTangents[j].y, mesh->mTangents[j].z);
//FProcMeshTangent meshTangent = FProcMeshTangent(v.X, v.Y, v.Z);
mi.Tangents.Push(v);
//mi.MeshTangents.Push(meshTangent);
}
//Vertex color
if (mesh->HasVertexColors(0))
{
FLinearColor color = FLinearColor(
mesh->mColors[0][j].r,
mesh->mColors[0][j].g,
mesh->mColors[0][j].b,
mesh->mColors[0][j].a
);
mi.VertexColors.Push(color);
}
}
}
}
static void FindMesh(const aiScene* scene, aiNode* node, FReturnedData& retdata, UVrmAssetListObject *vrmAssetList)
{
FindMeshInfo(scene, node, retdata, vrmAssetList);
for (uint32 m = 0; m < node->mNumChildren; ++m)
{
FindMesh(scene, node->mChildren[m], retdata, vrmAssetList);
}
}
static UPhysicsConstraintTemplate *createConstraint(USkeletalMesh *sk, UPhysicsAsset *pa, VRM::VRMSpring &spring, FName con1, FName con2){
UPhysicsConstraintTemplate *ct = NewObject<UPhysicsConstraintTemplate>(pa, NAME_None, RF_Transactional);
pa->ConstraintSetup.Add(ct);
//"skirt_01_01"
ct->Modify(false);
{
FString orgname = con1.ToString() + TEXT("_") + con2.ToString();
FString cname = orgname;
int Index = 0;
while(pa->FindConstraintIndex(*cname) != INDEX_NONE)
{
cname = FString::Printf(TEXT("%s_%d"), *orgname, Index++);
}
ct->DefaultInstance.JointName = *cname;
}
ct->DefaultInstance.ConstraintBone1 = con1;
ct->DefaultInstance.ConstraintBone2 = con2;
ct->DefaultInstance.SetAngularSwing1Limit(EAngularConstraintMotion::ACM_Limited, 10);
ct->DefaultInstance.SetAngularSwing2Limit(EAngularConstraintMotion::ACM_Limited, 10);
ct->DefaultInstance.SetAngularTwistLimit(EAngularConstraintMotion::ACM_Limited, 10);
ct->DefaultInstance.ProfileInstance.ConeLimit.Stiffness = 100.f * spring.stiffness;
ct->DefaultInstance.ProfileInstance.TwistLimit.Stiffness = 100.f * spring.stiffness;
const int32 BoneIndex1 = VRMGetRefSkeleton(sk).FindBoneIndex(ct->DefaultInstance.ConstraintBone1);
const int32 BoneIndex2 = VRMGetRefSkeleton(sk).FindBoneIndex(ct->DefaultInstance.ConstraintBone2);
if (BoneIndex1 == INDEX_NONE || BoneIndex2 == INDEX_NONE) {
#if WITH_EDITOR
if (VRMConverter::IsImportMode()) {
ct->PostEditChange();
}
#endif
return ct;
}
check(BoneIndex1 != INDEX_NONE);
check(BoneIndex2 != INDEX_NONE);
const TArray<FTransform> &t = VRMGetRefSkeleton(sk).GetRawRefBonePose();
const FTransform BoneTransform1 = t[BoneIndex1];//sk->GetBoneTransform(BoneIndex1);
FTransform BoneTransform2 = t[BoneIndex2];//EditorSkelComp->GetBoneTransform(BoneIndex2);
{
int32 tb = BoneIndex2;
while (1) {
int32 p = VRMGetRefSkeleton(sk).GetRawParentIndex(tb);
if (p < 0) break;
const FTransform &f = t[p];
//BoneTransform2 = f.GetRelativeTransform(BoneTransform2);
if (p == BoneIndex1) {
break;
}
tb = p;
}
}
//auto b = BoneTransform1;
ct->DefaultInstance.Pos1 = FVector::ZeroVector;
ct->DefaultInstance.PriAxis1 = FVector(1, 0, 0);
ct->DefaultInstance.SecAxis1 = FVector(0, 1, 0);
auto r = BoneTransform2;// .GetRelativeTransform(BoneTransform2);
// auto r = BoneTransform1.GetRelativeTransform(BoneTransform2);
auto twis = r.GetLocation().GetSafeNormal();
auto p1 = twis;
p1.X = p1.Z = 0.f;
auto p2 = FVector::CrossProduct(twis, p1).GetSafeNormal();
p1 = FVector::CrossProduct(p2, twis).GetSafeNormal();
ct->DefaultInstance.Pos2 = -r.GetLocation();
//ct->DefaultInstance.PriAxis2 = p1;
//ct->DefaultInstance.SecAxis2 = p2;
ct->DefaultInstance.PriAxis2 = r.GetUnitAxis(EAxis::X);
ct->DefaultInstance.SecAxis2 = r.GetUnitAxis(EAxis::Y);
// child
//ct->DefaultInstance.SetRefFrame(EConstraintFrame::Frame1, FTransform::Identity);
// parent
//ct->DefaultInstance.SetRefFrame(EConstraintFrame::Frame2, BoneTransform1.GetRelativeTransform(BoneTransform2));
#if WITH_EDITOR
ct->SetDefaultProfile(ct->DefaultInstance);
if (VRMConverter::IsImportMode()) {
ct->PostEditChange();
}
#endif
//ct->DefaultInstance.InitConstraint();
return ct;
}
static void CreateSwingTail(UVrmAssetListObject *vrmAssetList, VRM::VRMSpring &spring, FName &boneName, USkeletalBodySetup *bs, int BodyIndex1,
TArray<int> &swingBoneIndexArray, int sboneIndex = -1) {
USkeletalMesh *sk = vrmAssetList->SkeletalMesh;
USkeleton *k = VRMGetSkeleton(sk);
UPhysicsAsset *pa = VRMGetPhysicsAsset(sk);
TArray<int32> child;
int32 ii = k->GetReferenceSkeleton().FindBoneIndex(boneName);
GetChildBoneLocal(k, ii, child);
for (auto &c : child) {
if (sboneIndex >= 0) {
c = sboneIndex;
}
if (addedList.Find(k->GetReferenceSkeleton().GetBoneName(c).ToString().ToLower()) >= 0) {
continue;
}
addedList.Add(k->GetReferenceSkeleton().GetBoneName(c).ToString().ToLower());
USkeletalBodySetup *bs2 = Cast<USkeletalBodySetup>(StaticDuplicateObject(bs, pa, NAME_None));
bs2->BoneName = k->GetReferenceSkeleton().GetBoneName(c);
bs2->PhysicsType = PhysType_Simulated;
bs2->CollisionReponse = EBodyCollisionResponse::BodyCollision_Enabled;
//bs2->profile
int BodyIndex2 = pa->SkeletalBodySetups.Add(bs2);
auto *ct = createConstraint(sk, pa, spring, boneName, bs2->BoneName);
pa->DisableCollision(BodyIndex1, BodyIndex2);
swingBoneIndexArray.AddUnique(BodyIndex2);
//CreateSwingTail(vrmAssetList, spring, bs2->BoneName, bs2, BodyIndex2, swingBoneIndexArray);
if (sboneIndex >= 0) {
break;
}
}
}
static void CreateSwingHead(UVrmAssetListObject *vrmAssetList, VRM::VRMSpring &spring, FName &boneName, TArray<int> &swingBoneIndexArray, int sboneIndex) {
USkeletalMesh *sk = vrmAssetList->SkeletalMesh;
USkeleton *k = VRMGetSkeleton(sk);
UPhysicsAsset *pa = VRMGetPhysicsAsset(sk);
{
int i = VRMGetRefSkeleton(sk).FindRawBoneIndex(boneName);
if (i == INDEX_NONE) {
return;
}
}
//sk->RefSkeleton->GetParentIndex();
USkeletalBodySetup *bs = nullptr;
int BodyIndex1 = -1;
if (addedList.Find(boneName.ToString().ToLower()) < 0) {
addedList.Add(boneName.ToString().ToLower());
bs = NewObject<USkeletalBodySetup>(pa, NAME_None, RF_Transactional);
//int nodeID = aiData->mRootNode->FindNode(sbone.c_str());
//sk->RefSkeleton.GetRawRefBoneInfo[0].
//sk->bonetree
//bs->constrai
FKAggregateGeom agg;
FKSphereElem SphereElem;
SphereElem.Center = FVector(0);
SphereElem.Radius = spring.hitRadius * 100.f;
agg.SphereElems.Add(SphereElem);
bs->Modify();
bs->BoneName = boneName;
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 = 10.0f * spring.dragForce;
bs->DefaultInstance.AngularDamping = 10.0f * spring.dragForce;
bs->InvalidatePhysicsData();
bs->CreatePhysicsMeshes();
BodyIndex1 = pa->SkeletalBodySetups.Add(bs);
//pa->UpdateBodySetupIndexMap();
#if WITH_EDITOR
//pa->InvalidateAllPhysicsMeshes();
#endif
} else {
for (int i = 0; i < pa->SkeletalBodySetups.Num(); ++i) {
auto &a = pa->SkeletalBodySetups[i];
if (a->BoneName != boneName) {
continue;
}
BodyIndex1 = i;
bs = a;
break;
}
}
if (BodyIndex1 >= 0) {
CreateSwingTail(vrmAssetList, spring, boneName, bs, BodyIndex1, swingBoneIndexArray);
}
}
bool VRMConverter::ConvertModel(UVrmAssetListObject *vrmAssetList) {
if (vrmAssetList == nullptr) {
return false;
}
vrmAssetList->MeshReturnedData = MakeShareable(new FReturnedData());
FReturnedData &result = *(vrmAssetList->MeshReturnedData);
//FReturnedData &result = *(vrmAssetList->Result);
result.bSuccess = false;
result.meshInfo.Empty();
result.NumMeshes = 0;
if (aiData == nullptr)
{
UE_LOG(LogVRM4ULoader, Warning, TEXT("test null.\n"));
}
if (aiData->HasMeshes() && VRMConverter::Options::Get().IsDebugNoMesh() == false)
{
// !! before remove unused vertex !!
// remove degenerate triangles
//
if (Options::Get().IsRemoveDegenerateTriangles()) {
for (uint32 meshNo = 0; meshNo < aiData->mNumMeshes; ++meshNo)
{
//Triangle number
for (uint32 faceNo = 0; faceNo < aiData->mMeshes[meshNo]->mNumFaces; ++faceNo)
{
for (uint32 indexNo = 0; indexNo < aiData->mMeshes[meshNo]->mFaces[faceNo].mNumIndices; ++indexNo)
{
if (indexNo >= 3) {
//UE_LOG(LogVRM4ULoader, Warning, TEXT("FindMeshInfo. %d\n"), m);
}
if ((indexNo % 3 == 0) && (indexNo + 2 < aiData->mMeshes[meshNo]->mFaces[faceNo].mNumIndices)) {
const unsigned int tmpIndex[] = {
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo],
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo + 1],
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo + 2],
};
aiVector3D v[] = {
aiData->mMeshes[meshNo]->mVertices[tmpIndex[0]],
aiData->mMeshes[meshNo]->mVertices[tmpIndex[1]],
aiData->mMeshes[meshNo]->mVertices[tmpIndex[2]],
};
v[0] -= v[2];
v[1] -= v[2];
bool bDel = false;
if ((v[0] ^ v[1]).SquareLength() == 0) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("degenerate face %d"), faceNo);
// del
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo] = 0;
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo+1] = 0;
aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo+2] = 0;
indexNo += 2;
continue;
}
}
}
}
}
}
result.meshInfo.SetNum(aiData->mNumMeshes);
// find and remove unused vertex
FindMesh(aiData, aiData->mRootNode, result, vrmAssetList);
for (uint32 meshNo = 0; meshNo < aiData->mNumMeshes; ++meshNo)
{
//Triangle number
for (uint32 faceNo = 0; faceNo < aiData->mMeshes[meshNo]->mNumFaces; ++faceNo)
{
for (uint32 indexNo = 0; indexNo < aiData->mMeshes[meshNo]->mFaces[faceNo].mNumIndices; ++indexNo)
{
if (indexNo >= 3) {
//UE_LOG(LogVRM4ULoader, Warning, TEXT("FindMeshInfo. %d\n"), m);
}
if ((indexNo % 3 == 0) && (indexNo + 2 < aiData->mMeshes[meshNo]->mFaces[faceNo].mNumIndices)) {
int tmp = aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo]
+ aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo + 1]
+ aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo + 2];
if (tmp == 0) {
// remove
indexNo += 2;
continue;
}
}
result.meshInfo[meshNo].Triangles.Push(aiData->mMeshes[meshNo]->mFaces[faceNo].mIndices[indexNo]);
}
}
}
result.bSuccess = true;
}
bool bReimportMode = false;
USkeletalMesh *sk = nullptr;
if (vrmAssetList->Package == GetTransientPackage()) {
sk = VRM4U_NewObject<USkeletalMesh>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient);
}else {
TArray<UObject*> ret;
FString name = (FString(TEXT("SK_")) + vrmAssetList->BaseFileName);
bool bExistAsset = false;
GetObjectsWithOuter(vrmAssetList->Package, ret);
for (auto *a : ret) {
auto s = a->GetName().ToLower();
if (s.IsEmpty()) continue;
if (s == name.ToLower()) {
static int ccc = 0;
++ccc;
if (Options::Get().IsForceOverride()) {
a->Rename(*(FString(TEXT("need_reload_sk_VRM"))+FString::FromInt(ccc)), GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_ForceNoResetLoaders);
} else {
bReimportMode = true;
// reimport
bExistAsset = true;
sk = Cast<USkeletalMesh>(a);
sk->MarkPackageDirty();
}
break;
}
}
if (bExistAsset == false) {
sk = VRM4U_NewObject<USkeletalMesh>(vrmAssetList->Package, *name, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
}
#if UE_VERSION_OLDER_THAN(5,1,0)
#else
if (sk) {
UVrmAssetUserData*d = NewObject<UVrmAssetUserData>(sk, NAME_None, RF_Public | RF_Transactional);
d->VrmAssetListObject = vrmAssetList;
sk->AddAssetUserData(d);
}
#endif
{
#if WITH_EDITOR
sk->PreEditChange(NULL);
//Dirty the DDC Key for any imported Skeletal Mesh
#if UE_VERSION_OLDER_THAN(4,24,0)
#else
sk->InvalidateDeriveDataCacheGUID();
#endif
if (bReimportMode == false) {
FSkeletalMeshModel* ImportedResource = sk->GetImportedModel();
ImportedResource->LODModels.Empty();
}
#endif
if (bReimportMode == false) {
sk->ReleaseCPUResources();
sk->ReleaseResources();
#if UE_VERSION_OLDER_THAN(4,24,0)
#else
sk->ReleaseSkinWeightProfileResources();
#endif
#if UE_VERSION_OLDER_THAN(4,27,0)
sk->PhysicsAsset = nullptr;
sk->BodySetup = nullptr;
#else
sk->SetPhysicsAsset(nullptr);
sk->SetBodySetup(nullptr);
#endif
}
}
bool bCreateSkeleton = false;
USkeleton *k = Options::Get().GetSkeleton();
if (k == nullptr){
bCreateSkeleton = true;
if (vrmAssetList->Package == GetTransientPackage()) {
k = VRM4U_NewObject<USkeleton>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient);
}else {
bool bExistAsset = false;
TArray<UObject*> ret;
FString name = (TEXT("SKEL_") + vrmAssetList->BaseFileName);
GetObjectsWithOuter(vrmAssetList->Package, ret);
if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) {
FString s = VRM4U_GetPackagePath(vrmAssetList->Package) / name + TEXT(".")+ name;
auto *a = FindObject<USkeleton>(NULL, *s);
if (a != nullptr) {
ret.Add(a);
}
}
for (auto *a : ret) {
auto s = a->GetName().ToLower();
if (s.IsEmpty()) continue;
if (s == name.ToLower()) {
if (Options::Get().IsForceOverride()) {
a->ClearFlags(EObjectFlags::RF_Standalone);
a->SetFlags(EObjectFlags::RF_Public | RF_Transient);
a->Rename(*(FString(TEXT("need_reload_k_VRM")) + FString::FromInt(0)), GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_ForceNoResetLoaders);
a->ConditionalBeginDestroy();
} else {
// reimport
k = Cast<USkeleton>(a);
bExistAsset = true;
}
break;
}
}
if (bExistAsset == false) {
k = VRM4U_NewObject<USkeleton>(vrmAssetList->Package, *name, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
}
}
int allIndex = 0;
int allVertex = 0;
int uvNum = 1;
{
for (int meshID = 0; meshID < result.meshInfo.Num(); ++meshID) {
allIndex += result.meshInfo[meshID].Triangles.Num();
allVertex += result.meshInfo[meshID].Vertices.Num();
}
for (int meshID = 0; meshID < result.meshInfo.Num(); ++meshID) {
auto &mInfo = result.meshInfo[meshID];
uvNum = FMath::Max(uvNum, mInfo.UV0.Num());
uvNum = FMath::Min(uvNum, (int)MAX_TEXCOORDS);
if (uvNum >= 2) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("test uv2.\n"));
}
}
}
static int boneOffset = 0;
{
// name dup check
VRMSetSkeleton(sk, k);
//k->MergeAllBonesToBoneTree(src);
{
USkeletalMesh* sk_tmp = VRM4U_NewObject<USkeletalMesh>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient);
//USkeleton *k_tmp = VRM4U_NewObject<USkeleton>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient);
VRMSkeleton::readVrmBone(const_cast<aiScene*>(aiData), boneOffset, VRMGetRefSkeleton(sk_tmp), vrmAssetList);
// force set PMX bonetable for addIK
if (VRMConverter::Options::Get().IsPMXModel()) {
// add meta 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 = VRMGetRefSkeleton(sk_tmp).FindBoneIndex(*target);
if (ind != INDEX_NONE) {
for (const auto& v : VRMUtil::table_ue4_vrm) {
if (v.BoneUE4 == t.BoneUE4) {
if (v.BoneVRM.IsEmpty()) {
continue;
}
// renew bonemap
vrmAssetList->VrmMetaObject->humanoidBoneTable.Add(v.BoneVRM) = target;
finish = true;
break;
}
}
}
if (finish) break;
}// 2 loop
vrmAssetList->VrmMetaObject->humanoidBoneTable.Add("leftEye") = TEXT("左目");
vrmAssetList->VrmMetaObject->humanoidBoneTable.Add("rightEye") = TEXT("右目");
}
}// pmx bonetable end
VRMSkeleton::addIKBone(vrmAssetList, sk_tmp);
//k_tmp->readVrmBone(const_cast<aiScene*>(aiData), boneOffset);
//k_tmp->addIKBone(vrmAssetList);
//sk_tmp->Skeleton = k_tmp;
//sk_tmp->RefSkeleton = k_tmp->GetReferenceSkeleton();
//k_tmp->SetPreviewMesh(sk_tmp);
//k_tmp->UpdateReferencePoseFromMesh(sk_tmp);
//k_tmp->RecreateBoneTree(sk_tmp);
bool b = k->MergeAllBonesToBoneTree(sk_tmp);
if (b == false) {
// import failure
UE_LOG(LogVRM4ULoader, Error, TEXT("VRM: Failed MergeAllBonesToBoneTree \"%s\" to \"%s\""), *sk->GetName(), *k->GetName());
VRMSetSkeleton(sk, nullptr);
sk->ClearFlags(EObjectFlags::RF_Standalone);
sk->SetFlags(EObjectFlags::RF_Public | RF_Transient);
sk->ConditionalBeginDestroy();
if (bCreateSkeleton) {
k->ClearFlags(EObjectFlags::RF_Standalone);
k->SetFlags(EObjectFlags::RF_Public | RF_Transient);
k->ConditionalBeginDestroy();
}
return false;
}
VRMSetRefSkeleton(sk, k->GetReferenceSkeleton());
if (Options::Get().GetSkeleton()) {
// update refpose
FReferenceSkeletonModifier RefSkelModifier(VRMGetRefSkeleton(sk), k);
{
FTransform t;
t.SetIdentity();
const auto& srcRef = VRMGetRefSkeleton(sk_tmp);
auto& dstRef = VRMGetRefSkeleton(sk);
for (int c = 0; c < srcRef.GetRawBoneNum(); ++c) {
auto name = srcRef.GetBoneName(c);
auto ind = dstRef.FindRawBoneIndex(name);
if (ind >= 0) {
auto pose = srcRef.GetRawRefBonePose()[c];
RefSkelModifier.UpdateRefPoseTransform(ind, pose);
}
}
//for (int i = 0; i < sk->RefSkeleton.GetRawBoneNum(); ++i) {
// RefSkelModifier.UpdateRefPoseTransform(i, t);
//}
}
}
}
sk->CalculateInvRefMatrices();
sk->CalculateExtendedBounds();
#if WITH_EDITOR
#if UE_VERSION_OLDER_THAN(4,20,0)
#else
sk->UpdateGenerateUpToData();
#endif
sk->ConvertLegacyLODScreenSize();
sk->GetImportedModel()->LODModels.Reset();
#endif
#if WITH_EDITORONLY_DATA
k->SetPreviewMesh(sk);
#endif
if (Options::Get().GetSkeleton() == nullptr) {
k->RecreateBoneTree(sk);
}
#if UE_VERSION_OLDER_THAN(5,0,0)
{
TArray<FName> BonesToRemove;
k->RemoveVirtualBones(BonesToRemove);
}
#endif
// sk end
vrmAssetList->SkeletalMesh = sk;
if (sk->GetLODInfo(0) == nullptr) {
#if UE_VERSION_OLDER_THAN(4,20,0)
sk->LODInfo.AddZeroed(1);
//const USkeletalMeshLODSettings* DefaultSetting = sk->GetDefaultLODSetting();
// if failed to get setting, that means, we don't have proper setting
// in that case, use last index setting
//!DefaultSetting->SetLODSettingsToMesh(sk, 0);
#else
FSkeletalMeshLODInfo& info = sk->AddLODInfo();
#endif
}
if (VRMConverter::Options::Get().IsVRM10Model() && VRMConverter::Options::Get().IsVRM10Bindpose() == false
&& VRMConverter::Options::Get().IsDebugOneBone() == false
&& VRMConverter::Options::Get().IsVRM10BindToRestPose() == true) {
if (vrmAssetList->Pose_bind.Num() == 0 || vrmAssetList->Pose_tpose.Num() == 0) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("BindPose -> TPose :: no bindpose array!"));
}
else {
auto& info = vrmAssetList->MeshReturnedData->meshInfo;
struct WeightData {
FString boneName;
float weight = 0;
};
TMap<int, TArray<WeightData> > weightTable;
auto* scene = const_cast<aiScene*>(aiData);
// generate weightTable
{
int vertexOffset = 0;
for (uint32_t meshNo = 0; meshNo < scene->mNumMeshes; ++meshNo) {
auto* mesh = scene->mMeshes[meshNo];
for (uint32_t boneNo = 0; boneNo < mesh->mNumBones; ++boneNo) {
auto* bone = mesh->mBones[boneNo];
for (uint32_t weightNo = 0; weightNo < bone->mNumWeights; ++weightNo) {
auto weight = bone->mWeights[weightNo];
WeightData d;
d.boneName = UTF8_TO_TCHAR(bone->mName.C_Str());
d.weight = weight.mWeight;
weightTable.FindOrAdd(vertexOffset + weight.mVertexId).Add(d);
}
}
vertexOffset += mesh->mNumVertices;
}
// weight check
for (auto w : weightTable) {
float f = 0.f;
for (auto data : w.Value) {
f += data.weight;
}
if (fabs(f - 1.f) > 0.01f) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("BindPose -> TPose :: bad weight!"));
}
}
}// end weightTable
// bind pose -> t pose
{
int vertexOffset = 0;
for (uint32_t meshNo = 0; meshNo < scene->mNumMeshes; ++meshNo) {
if (info.IsValidIndex(meshNo) == false) {
break;
}
auto* mesh = scene->mMeshes[meshNo];
for (int vertexNo = 0; vertexNo < info[meshNo].Vertices.Num(); ++vertexNo) {
FVector v_orig = info[meshNo].Vertices[vertexNo];
//v_orig.Set(mesh->mVertices[vertexNo].x, mesh->mVertices[vertexNo].y, mesh->mVertices[vertexNo].z);
v_orig.Set(mesh->mVertices[vertexNo].x, -mesh->mVertices[vertexNo].z, mesh->mVertices[vertexNo].y);
FVector v(0, 0, 0);
if (weightTable.Find(vertexOffset + vertexNo) == nullptr) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("BindPose -> TPose :: no weight data %d"), vertexOffset + vertexNo);
continue;
}
for (auto a : weightTable[vertexOffset + vertexNo]) {
auto tpose = vrmAssetList->Pose_tpose.Find(a.boneName);
auto bpose = vrmAssetList->Pose_bind.Find(a.boneName);
if (tpose && bpose) {
FVector v_diff = (bpose->Inverse() * *tpose).TransformPosition(v_orig * 100.f);
v += v_diff * a.weight;
#if UE_VERSION_OLDER_THAN(5,1,0)
float len = v.Size();
#else
float len = v.Length();
#endif
if (len >= 100000) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("BindPose -> TPose :: bad weight!"));
}
}
else {
UE_LOG(LogVRM4ULoader, Warning, TEXT("BindPose -> TPose :: no pose transform %p %p"), tpose, bpose);
}
}
v.Set(v.X, v.Z, -v.Y);
info[meshNo].Vertices[vertexNo] = v / 100.f;
}
vertexOffset += mesh->mNumVertices;
}
}
}
}// end bind -> t pose
// begin vertex
//sk->CacheDerivedData();
if (bReimportMode == false) {
sk->AllocateResourceForRendering();
}
FSkeletalMeshRenderData* p = sk->GetResourceForRendering();
//p->Cache(sk);
//sk->OnPostMeshCached().Broadcast(sk);
if (p->LODRenderData.Num() == 0) {
#if UE_VERSION_OLDER_THAN(4,23,0)
new(p->LODRenderData) FSkeletalMeshLODRenderData();
#else
auto* tmp = new FSkeletalMeshLODRenderData();
p->LODRenderData.Add(tmp);
#endif
}
FSkeletalMeshLODRenderData* pRd = &p->LODRenderData[0];
{
if (allVertex > 0) {
pRd->StaticVertexBuffers.PositionVertexBuffer.Init(allVertex);
pRd->StaticVertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor(255, 255, 255, 255), allVertex);
pRd->StaticVertexBuffers.StaticMeshVertexBuffer.CleanUp();
pRd->StaticVertexBuffers.StaticMeshVertexBuffer.Init(allVertex, uvNum);
}
#if WITH_EDITOR
TArray<FSoftSkinVertex> Weight;
Weight.SetNum(allVertex);
pRd->SkinWeightVertexBuffer.Init(Weight);
#else
{
#if UE_VERSION_OLDER_THAN(4,25,0)
TArray< TSkinWeightInfo<false> > InWeights;
#else
TArray<FSkinWeightInfo> InWeights;
#endif
InWeights.SetNum(allVertex);
for (auto& a : InWeights) {
memset(a.InfluenceBones, 0, sizeof(a.InfluenceBones));
memset(a.InfluenceWeights, 0, sizeof(a.InfluenceWeights));
}
pRd->SkinWeightVertexBuffer = InWeights;
}
#endif
}
sk->InitResources();
if (VRMConverter::Options::Get().IsDebugNoMaterial()) {
//VRMGetMaterials(sk).SetNum(vrmAssetList->Materials.Num());
}else{
VRMGetMaterials(sk).SetNum(vrmAssetList->Materials.Num());
}
for (int i = 0; i < VRMGetMaterials(sk).Num(); ++i) {
VRMGetMaterials(sk)[i].MaterialInterface = vrmAssetList->Materials[i];
VRMGetMaterials(sk)[i].MaterialSlotName = UTF8_TO_TCHAR(aiData->mMaterials[i]->GetName().C_Str());
VRMGetMaterials(sk)[i].UVChannelData = FMeshUVChannelInfo(1);
}
if (VRMGetMaterials(sk).Num() == 0) {
VRMGetMaterials(sk).SetNum(1);
}
}
{
FSkeletalMeshLODRenderData &rd = sk->GetResourceForRendering()->LODRenderData[0];
FVector BoundMin(-100, -100, 0);
FVector BoundMax(100, 100, 200);
TArray<int> AllActiveBones;
{
int boneNum = VRMGetSkeleton(sk)->GetReferenceSkeleton().GetRawBoneNum();
rd.RequiredBones.SetNum(boneNum);
rd.ActiveBoneIndices.SetNum(boneNum);
for (int i = 0; i < boneNum; ++i) {
rd.RequiredBones[i] = i;
rd.ActiveBoneIndices[i] = i;
}
}
{
FStaticMeshVertexBuffers &v = rd.StaticVertexBuffers;
FSoftSkinVertexLocal softSkinVertexLocalZero;
{
#if UE_VERSION_OLDER_THAN(4,20,0)
{
FPackedNormal n(0);
softSkinVertexLocalZero.Position = FVector::ZeroVector;
softSkinVertexLocalZero.TangentX = softSkinVertexLocalZero.TangentY = softSkinVertexLocalZero.TangentZ = n;
}
#elif UE_VERSION_OLDER_THAN(5,0,0)
softSkinVertexLocalZero.Position = softSkinVertexLocalZero.TangentX = softSkinVertexLocalZero.TangentY = FVector::ZeroVector;
softSkinVertexLocalZero.TangentZ.Set(0, 0, 0, 1);
#else
softSkinVertexLocalZero.Position = softSkinVertexLocalZero.TangentX = softSkinVertexLocalZero.TangentY = FVector3f::Zero();
softSkinVertexLocalZero.TangentZ.Set(0, 0, 0, 1);
#endif
softSkinVertexLocalZero.Color = FColor::White;
memset(softSkinVertexLocalZero.UVs, 0, sizeof(softSkinVertexLocalZero.UVs));
memset(softSkinVertexLocalZero.InfluenceBones, 0, sizeof(softSkinVertexLocalZero.InfluenceBones));
memset(softSkinVertexLocalZero.InfluenceWeights, 0, sizeof(softSkinVertexLocalZero.InfluenceWeights));
}
TArray<uint32> Triangles;
TArray<FSoftSkinVertexLocal> Weight;
Weight.SetNum(allVertex);
for (auto &w : Weight) {
w = softSkinVertexLocalZero;
}
//Weight.AddZeroed(allVertex);
int currentIndex = 0;
int currentVertex = 0;
#if WITH_EDITORONLY_DATA
if (sk->GetImportedModel()->LODModels.Num() == 0) {
#if UE_VERSION_OLDER_THAN(4,23,0)
new(sk->GetImportedModel()->LODModels) FSkeletalMeshLODModel();
#else
sk->GetImportedModel()->LODModels.Add(new FSkeletalMeshLODModel());
#endif
}
sk->GetImportedModel()->LODModels[0].Sections.Empty();
sk->GetImportedModel()->LODModels[0].Sections.SetNum(result.meshInfo.Num());
#endif
if (VRMConverter::IsImportMode() == false) {
rd.RenderSections.Empty();
rd.RenderSections.SetNum(result.meshInfo.Num());
}
for (int meshID = 0; meshID < result.meshInfo.Num(); ++meshID) {
TArray<FSoftSkinVertexLocal> meshWeight;
auto &mInfo = result.meshInfo[meshID];
for (int i = 0; i < mInfo.Vertices.Num(); ++i) {
FSoftSkinVertexLocal *meshS = new(meshWeight) FSoftSkinVertexLocal();
*meshS = softSkinVertexLocalZero;
auto a = result.meshInfo[meshID].Vertices[i] * 100.f;
v.PositionVertexBuffer.VertexPosition(currentVertex + i).Set(-a.X, a.Z, a.Y);
if (VRMConverter::Options::Get().IsVRM10Model()) {
v.PositionVertexBuffer.VertexPosition(currentVertex + i).Set(a.X, -a.Z, a.Y);
}else if (VRMConverter::Options::Get().IsPMXModel() || VRMConverter::Options::Get().IsBVHModel()) {
v.PositionVertexBuffer.VertexPosition(currentVertex + i).X *= -1.f;
v.PositionVertexBuffer.VertexPosition(currentVertex + i).Y *= -1.f;
}
v.PositionVertexBuffer.VertexPosition(currentVertex + i) *= VRMConverter::Options::Get().GetModelScale();
for (int u=0; u < FMath::Min(mInfo.UV0.Num(), (int)MAX_TEXCOORDS); ++u){
FVector2D uv(0, 0);
if (i < mInfo.UV0[u].Num()) {
uv = mInfo.UV0[u][i];
}
#if UE_VERSION_OLDER_THAN(5,0,0)
v.StaticMeshVertexBuffer.SetVertexUV(currentVertex + i, u, uv);
meshS->UVs[u] = uv;
#else
v.StaticMeshVertexBuffer.SetVertexUV(currentVertex + i, u, FVector2f(uv));
meshS->UVs[u] = FVector2f(uv);
#endif
}
if (i < mInfo.Tangents.Num()){
//v.StaticMeshVertexBuffer.SetVertexTangents(currentVertex + i, FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1));
//v.StaticMeshVertexBuffer.SetVertexTangents(currentVertex + i, result.meshInfo[meshID].Tangents);
auto &n = mInfo.Normals[i];
FVector n_tmp(-n.X, n.Z, n.Y);
FVector t_tmp(-mInfo.Tangents[i].X, mInfo.Tangents[i].Z, mInfo.Tangents[i].Y);
if (VRMConverter::Options::Get().IsVRM10Model()) {
n_tmp.Set(n.X, -n.Z, n.Y);
t_tmp.Set(mInfo.Tangents[i].X, -mInfo.Tangents[i].Z, mInfo.Tangents[i].Y);
}
if (VRMConverter::Options::Get().IsPMXModel() || VRMConverter::Options::Get().IsBVHModel()) {
FVector tmpv(-1, -1, 1);
n_tmp *= tmpv;
t_tmp *= tmpv;
}
t_tmp.Normalize();
n_tmp.Normalize();
#if UE_VERSION_OLDER_THAN(5,0,0)
meshS->TangentX = t_tmp;
meshS->TangentY = n_tmp ^ t_tmp;
meshS->TangentZ = n_tmp;
#else
meshS->TangentX = FVector3f(t_tmp);
meshS->TangentY = FVector3f(n_tmp ^ t_tmp);
meshS->TangentZ = FVector4f(FVector3f(n_tmp), 1);
#endif
v.StaticMeshVertexBuffer.SetVertexTangents(currentVertex + i, meshS->TangentX, meshS->TangentY, meshS->TangentZ);
}
if (i < mInfo.VertexColors.Num()) {
auto &c = mInfo.VertexColors[i];
meshS->Color = FColor(c.R, c.G, c.B, c.A);
}
FSoftSkinVertexLocal &s = Weight[currentVertex + i];
s.Position = v.PositionVertexBuffer.VertexPosition(currentVertex + i);
meshS->Position = v.PositionVertexBuffer.VertexPosition(currentVertex + i);
} // vertex loop
auto &aiM = aiData->mMeshes[meshID];
TArray<int> bonemap;
TArray<BoneMapOpt> boneAll;
/* skip! no first bone
{
bonemap.Add(0);
BoneMapOpt o;
o.boneIndex = 0;
o.weight = -1.f;
boneAll.Add(o);
}
*/
//aiData->mRootNode->mMeshes
for (uint32 boneIndex = 0; boneIndex < aiM->mNumBones; ++boneIndex) {
auto& aiB = aiM->mBones[boneIndex];
const int b = VRMGetRefSkeleton(sk).FindBoneIndex(UTF8_TO_TCHAR(aiB->mName.C_Str()));
if (b < 0) {
continue;
}
for (uint32 weightIndex = 0; weightIndex < aiB->mNumWeights; ++weightIndex) {
auto& aiW = aiB->mWeights[weightIndex];
if (aiW.mWeight == 0.f) {
continue;
}
for (int jj = 0; jj < MAX_TOTAL_INFLUENCES; ++jj) {
if (Weight.IsValidIndex(aiW.mVertexId + currentVertex) == false) {
continue;
}
auto& s = Weight[aiW.mVertexId + currentVertex];
if (s.InfluenceWeights[jj] > 0) {
continue;
}
const float ww = FMath::Clamp(aiW.mWeight, 0.f, 1.f);
if (ww < VRM4U_BoneWeightThreshold) {
continue;
}
bonemap.AddUnique(b);
AllActiveBones.AddUnique(b);
}
}
}
bonemap.Sort();
for (uint32 boneIndex = 0; boneIndex < aiM->mNumBones; ++boneIndex) {
auto &aiB = aiM->mBones[boneIndex];
const int b = VRMGetRefSkeleton(sk).FindBoneIndex(UTF8_TO_TCHAR(aiB->mName.C_Str()));
if (b < 0) {
continue;
}
for (uint32 weightIndex = 0; weightIndex < aiB->mNumWeights; ++weightIndex) {
auto &aiW = aiB->mWeights[weightIndex];
if (aiW.mWeight == 0.f) {
continue;
}
for (int jj = 0; jj < MAX_TOTAL_INFLUENCES; ++jj) {
if (Weight.IsValidIndex(aiW.mVertexId + currentVertex) == false) {
continue;
}
auto &s = Weight[aiW.mVertexId + currentVertex];
if (s.InfluenceWeights[jj] > 0) {
continue;
}
const float ww = FMath::Clamp(aiW.mWeight, 0.f, 1.f);
if (ww < VRM4U_BoneWeightThreshold) {
continue;
}
int tabledIndex = bonemap.AddUnique(b);
if (tabledIndex == INDEX_NONE) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("bonemap add error!"));
tabledIndex = 0;
}
if (tabledIndex > 255) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("bonemap over!"));
}
if (Options::Get().IsDebugOneBone()) {
tabledIndex = 0;
}
s.InfluenceBones[jj] = tabledIndex;
//s.InfluenceWeights[jj] = (VRM4U_BONE_INFLUENCT_TYPE)FMath::TruncToInt(ww * 255.f + (0.5f - KINDA_SMALL_NUMBER));
s.InfluenceWeights[jj] = (VRM4U_BONE_INFLUENCE_TYPE)FMath::TruncToInt(ww * VRM4U_MaxBoneWeightFloat);
meshWeight[aiW.mVertexId].InfluenceBones[jj] = s.InfluenceBones[jj];
meshWeight[aiW.mVertexId].InfluenceWeights[jj] = s.InfluenceWeights[jj];
if (Options::Get().IsMobileBone()){
auto p = boneAll.FindByPredicate([&](BoneMapOpt &o) {return o.boneIndex == b; });
if (p) {
p->weight += aiW.mWeight;
} else {
BoneMapOpt o;
o.boneIndex = b;
o.weight = aiW.mWeight;
boneAll.Add(o);
}
}
break;
}
}
}// bone loop
// mobile remap
if (Options::Get().IsMobileBone() && boneAll.Num() > 75) {
TMap<int, int> mobileMap;
auto bonemapNew = bonemap;
while (boneAll.Num() > 75) {
boneAll.Sort();
// bone 0 == weight 0
// search from 1
auto &removed = boneAll[1];
int findParent = removed.boneIndex;
while (findParent >= 0) {
findParent = VRMGetRefSkeleton(sk).GetParentIndex(findParent);
auto p = boneAll.FindByPredicate([&](const BoneMapOpt &o) {return o.boneIndex == findParent;});
if (p == nullptr) {
continue;
}
p->weight += removed.weight;
while(auto a = mobileMap.FindKey(removed.boneIndex) ){
mobileMap[*a] = p->boneIndex;
}
mobileMap.FindOrAdd(removed.boneIndex) = p->boneIndex;
break;
}
bonemapNew.Remove(removed.boneIndex);
boneAll.RemoveAt(1);
}
if (mobileMap.Num()) {
for (auto &a : Weight) {
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
auto &infBone = a.InfluenceBones[i];
auto &infWeight = a.InfluenceWeights[i];
if (bonemap.IsValidIndex(infBone) == false) {
infWeight = 0.f;
infBone = 0;
continue;
}
auto srcBoneIndex = bonemap[infBone];
auto f = mobileMap.Find(srcBoneIndex);
if (f) {
auto dstBoneIndex = *f;
infBone = bonemapNew.IndexOfByKey(dstBoneIndex);
} else {
infBone = bonemapNew.IndexOfByKey(srcBoneIndex);
}
}
}
for (auto &a : meshWeight) {
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
auto &infBone = a.InfluenceBones[i];
auto &infWeight = a.InfluenceWeights[i];
if (bonemap.IsValidIndex(infBone) == false) {
infWeight = 0.f;
infBone = 0;
continue;
}
auto srcBoneIndex = bonemap[infBone];
auto f = mobileMap.Find(srcBoneIndex);
if (f) {
auto dstBoneIndex = *f;
infBone = bonemapNew.IndexOfByKey(dstBoneIndex);
} else {
infBone = bonemapNew.IndexOfByKey(srcBoneIndex);
}
}
}
}
bonemap = bonemapNew;
}// mobile remap
// normalize weight
for (auto& w : meshWeight) {
static int warnCount = 0;
if (&w == &meshWeight[0]) {
warnCount = 0;
}
// sort by Weight
for (int i = 0; i < MAX_TOTAL_INFLUENCES - 1; ++i) {
for (int j = i + 1; j < MAX_TOTAL_INFLUENCES; ++j) {
if (w.InfluenceWeights[i] < w.InfluenceWeights[j]) {
std::swap(w.InfluenceWeights[i], w.InfluenceWeights[j]);
std::swap(w.InfluenceBones[i], w.InfluenceBones[j]);
}
}
}
int f = 0;
int maxIndex = 0;
int maxWeight = 0;
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
f += w.InfluenceWeights[i];
if (maxWeight < w.InfluenceWeights[i]) {
maxWeight = w.InfluenceWeights[i];
maxIndex = i;
}
}
if (f > VRM4U_MaxBoneWeight) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("overr"));
w.InfluenceWeights[0] -= (VRM4U_BONE_INFLUENCE_TYPE)(f - VRM4U_MaxBoneWeight);
}
if (1) {
if (f <= (VRM4U_MaxBoneWeight - MAX_TOTAL_INFLUENCES)) {
if (warnCount < 50) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("less"));
warnCount++;
}
}
if (f == 0) {
auto* p = GetNodeFromMeshID(meshID, aiData);
int dummy = 0;
if (p) {
dummy = VRMGetRefSkeleton(sk).FindBoneIndex(UTF8_TO_TCHAR(p->mName.C_Str()));
if (dummy == INDEX_NONE) {
dummy = 0;
}
// add active bone for simple static mesh (not skinned mesh)
AllActiveBones.AddUnique(dummy);
}
auto inf = bonemap.AddUnique(dummy);
if (inf == INDEX_NONE) {
inf = 0;
}
w.InfluenceBones[0] = inf;
w.InfluenceWeights[0] += (VRM4U_BONE_INFLUENCE_TYPE)(VRM4U_MaxBoneWeight - f);
} else {
const int n = Options::Get().GetBoneWeightInfluenceNum();
{
// recalc weight
f = 0;
for (int i = 0; i < n; ++i) {
f += w.InfluenceWeights[i];
}
if (f == 0) {
f = 1;
}
for (int i = n; i < MAX_TOTAL_INFLUENCES; ++i) {
w.InfluenceBones[i] = 0;
w.InfluenceWeights[i] = 0;
}
int total = 0;
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
int t = w.InfluenceWeights[i];
w.InfluenceWeights[i] = (VRM4U_BONE_INFLUENCE_TYPE)FMath::TruncToInt(VRM4U_MaxBoneWeightFloat * t / f);
total += w.InfluenceWeights[i];
}
// adjust
if (total > VRM4U_MaxBoneWeight) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("overr"));
w.InfluenceWeights[0] -= (VRM4U_BONE_INFLUENCE_TYPE)(total - VRM4U_MaxBoneWeight);
}
w.InfluenceWeights[0] += (VRM4U_BONE_INFLUENCE_TYPE)(VRM4U_MaxBoneWeight - total);
}
}
}
}// nomalize weight
if (VRMConverter::IsImportMode() == false) {
FSkelMeshRenderSection &NewRenderSection = rd.RenderSections[meshID];
bool bUseMergeMaterial = Options::Get().IsMergeMaterial();
if ((int)aiM->mMaterialIndex >= vrmAssetList->MaterialMergeTable.Num()) {
bUseMergeMaterial = false;
}
if (bUseMergeMaterial) {
NewRenderSection.MaterialIndex = vrmAssetList->MaterialMergeTable[aiM->mMaterialIndex];// ModelSection.MaterialIndex;
}else {
NewRenderSection.MaterialIndex = aiM->mMaterialIndex;// ModelSection.MaterialIndex;
}
if (NewRenderSection.MaterialIndex >= vrmAssetList->Materials.Num()) NewRenderSection.MaterialIndex = 0;
NewRenderSection.BaseIndex = currentIndex;
NewRenderSection.NumTriangles = result.meshInfo[meshID].Triangles.Num() / 3;
//NewRenderSection.bRecomputeTangent = ModelSection.bRecomputeTangent;
NewRenderSection.bCastShadow = true;// ModelSection.bCastShadow;
NewRenderSection.BaseVertexIndex = currentVertex;// currentVertex;// currentVertex;// ModelSection.BaseVertexIndex;
//NewRenderSection.ClothMappingData = ModelSection.ClothMappingData;
//NewRenderSection.BoneMap.SetNum(1);//ModelSection.BoneMap;
//NewRenderSection.BoneMap[0] = 10;
//NewRenderSection.BoneMap.SetNum(sk->Skeleton->GetBoneTree().Num());//ModelSection.BoneMap;
//for (int i = 0; i < NewRenderSection.BoneMap.Num(); ++i) {
// NewRenderSection.BoneMap[i] = i;
//}
if (bonemap.Num() > 0) {
NewRenderSection.BoneMap.SetNum(bonemap.Num());//ModelSection.BoneMap;
for (int i = 0; i < NewRenderSection.BoneMap.Num(); ++i) {
NewRenderSection.BoneMap[i] = bonemap[i];
}
}else {
NewRenderSection.BoneMap.SetNum(1);
auto *p = GetNodeFromMeshID(meshID, aiData);
int32 i = k->GetReferenceSkeleton().FindBoneIndex(UTF8_TO_TCHAR(p->mName.C_Str()));
if (i <= 0) {
i = meshID;
}
NewRenderSection.BoneMap[0] = i;
}
NewRenderSection.NumVertices = result.meshInfo[meshID].Vertices.Num();// result.meshInfo[meshID].Triangles.Num();// allVertex;// result.meshInfo[meshID].Vertices.Num();// ModelSection.NumVertices;
NewRenderSection.MaxBoneInfluences = Options::Get().GetBoneWeightInfluenceNum();// ModelSection.MaxBoneInfluences;
//NewRenderSection.CorrespondClothAssetIndex = ModelSection.CorrespondClothAssetIndex;
//NewRenderSection.ClothingData = ModelSection.ClothingData;
TMap<int32, TArray<int32>> OverlappingVertices;
NewRenderSection.DuplicatedVerticesBuffer.Init(NewRenderSection.NumVertices, OverlappingVertices);
NewRenderSection.bDisabled = false;// ModelSection.bDisabled;
//RenderSections.Add(NewRenderSection);
//rd.RenderSections[0] = NewRenderSection;
}
#if WITH_EDITORONLY_DATA
{
auto &s = sk->GetImportedModel()->LODModels[0].Sections[meshID];
s.MaterialIndex = 0;
TMap<int32, TArray<int32>> OverlappingVertices;
{
bool bUseMergeMaterial = Options::Get().IsMergeMaterial();
if ((int)aiM->mMaterialIndex >= vrmAssetList->MaterialMergeTable.Num()) {
bUseMergeMaterial = false;
}
if (bUseMergeMaterial) {
s.MaterialIndex = vrmAssetList->MaterialMergeTable[aiM->mMaterialIndex];
} else {
s.MaterialIndex = aiM->mMaterialIndex;
}
}
if (s.MaterialIndex >= vrmAssetList->Materials.Num()) s.MaterialIndex = 0;
#if UE_VERSION_OLDER_THAN(4,24,0)
#else
s.OriginalDataSectionIndex = meshID;
#endif
s.BaseIndex = currentIndex;
s.NumTriangles = result.meshInfo[meshID].Triangles.Num() / 3;
s.BaseVertexIndex = currentVertex;
s.SoftVertices = meshWeight;
if (bonemap.Num() > 0) {
s.BoneMap.SetNum(bonemap.Num());//ModelSection.BoneMap;
for (int i = 0; i < s.BoneMap.Num(); ++i) {
s.BoneMap[i] = bonemap[i];
}
}else {
s.BoneMap.SetNum(1);
auto *p = GetNodeFromMeshID(meshID, aiData);
int32 i = k->GetReferenceSkeleton().FindBoneIndex(UTF8_TO_TCHAR(p->mName.C_Str()));
if (i <= 0) {
i = meshID;
}
s.BoneMap[0] = i;
}
s.NumVertices = meshWeight.Num();
s.MaxBoneInfluences = Options::Get().GetBoneWeightInfluenceNum();
}
#endif // editor only data
//rd.MultiSizeIndexContainer.CopyIndexBuffer(result.meshInfo[0].Triangles);
int t1 = Triangles.Num();
Triangles.Append(result.meshInfo[meshID].Triangles);
int t2 = Triangles.Num();
for (int i = t1; i < t2; ++i) {
Triangles[i] += currentVertex;
}
currentIndex += result.meshInfo[meshID].Triangles.Num();
currentVertex += result.meshInfo[meshID].Vertices.Num();
//rd.MultiSizeIndexContainer.update
} // mesh loop
if (Options::Get().IsMergePrimitive()) {
#if WITH_EDITORONLY_DATA
// merge lod model section
auto &LodModel = sk->GetImportedModel()->LODModels[0];
for (int meshID = LodModel.Sections.Num() - 1; meshID>0; --meshID) {
auto &s0 = LodModel.Sections[meshID-1];
auto &s1 = LodModel.Sections[meshID];
if (aiData->mMeshes[meshID]->mNumAnimMeshes > 0) {
// skip skin mesh
continue;
}
if (s1.NumVertices == 0) {
continue;
}
if (s0.MaterialIndex != s1.MaterialIndex) {
continue;
}
auto newBoneMap = s0.BoneMap;
for (auto &sv : s1.SoftVertices) {
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
if (sv.InfluenceWeights[i] == 0) {
continue;
}
int boneID = s1.BoneMap[sv.InfluenceBones[i]];
int ind = 0;
if (newBoneMap.Find(boneID, ind)) {
sv.InfluenceBones[i] = ind;
} else {
sv.InfluenceBones[i] = newBoneMap.Add(boneID);
}
}
}
if (Options::Get().IsMobileBone()) {
if (newBoneMap.Num() > 75) {
continue;
}
}
s0.SoftVertices.Append(s1.SoftVertices);
s0.NumVertices += s1.NumVertices;
s0.NumTriangles += s1.NumTriangles;
s0.BoneMap = newBoneMap;
s1.SoftVertices.SetNum(0);
s1.NumVertices = 0;
}
for (int meshID = 0; meshID < LodModel.Sections.Num(); ++meshID) {
auto &s1 = LodModel.Sections[meshID];
if (s1.NumVertices > 0) {
continue;
}
LodModel.Sections.RemoveAt(meshID);
meshID--;
}
#endif
} // merge primitive
if (1) {
int warnCount = 0;
for (auto &w : Weight) {
int f = 0;
int maxIndex = 0;
int maxWeight = 0;
#if UE_VERSION_OLDER_THAN(5,2,0)
#else
#if WITH_EDITOR
#else
for (int i = 0; i < Options::Get().GetBoneWeightInfluenceNum(); ++i) {
w.InfluenceWeights[i] = (VRM4U_BONE_INFLUENCE_TYPE)(((int)(w.InfluenceWeights[i] / 256)) * 256);
}
#endif
#endif
for (int i = 0; i < MAX_TOTAL_INFLUENCES; ++i) {
f += w.InfluenceWeights[i];
if (maxWeight < w.InfluenceWeights[i]) {
maxWeight = w.InfluenceWeights[i];
maxIndex = i;
}
}
if (f > VRM4U_MaxBoneWeight) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("overr"));
w.InfluenceWeights[0] -= (VRM4U_BONE_INFLUENCE_TYPE)(f - VRM4U_MaxBoneWeight);
}
if (f < VRM4U_MaxBoneWeight) {
if (f <= (VRM4U_MaxBoneWeight - MAX_TOTAL_INFLUENCES)) {
if (warnCount < 50) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("less"));
warnCount++;
}
}
w.InfluenceWeights[maxIndex] += (VRM4U_BONE_INFLUENCE_TYPE)(VRM4U_MaxBoneWeight - f);
}
}
}
#if WITH_EDITOR
{
FSkeletalMeshLODModel *p = &(sk->GetImportedModel()->LODModels[0]);
p->NumVertices = allVertex;
p->NumTexCoords = uvNum; // allVertex;
p->IndexBuffer = Triangles;
p->ActiveBoneIndices = rd.ActiveBoneIndices;
p->RequiredBones = rd.RequiredBones;
}
#else // game
#if UE_VERSION_OLDER_THAN(4,25,0)
#else
// 4.25
// force reinit renderdata
{
FSkeletalMeshRenderData* p = sk->GetResourceForRendering();
auto *pRd = &(p->LODRenderData[0]);
TArray<FSkinWeightInfo> InWeights;
InWeights.SetNum(Weight.Num());
int elem = 0;
{
if (Weight.Num()) {
elem = std::extent<decltype(Weight[0].InfluenceBones)>::value;
}
FSkinWeightInfo info = {};
elem = FMath::Min(elem, (int)std::extent<decltype(info.InfluenceBones)>::value);
};
for (int i = 0; i < Weight.Num(); ++i) {
FSkinWeightInfo info = {};
for (int al = 0; al < elem; ++al) {
// uint8 <> uint16
info.InfluenceBones[al] = Weight[i].InfluenceBones[al];
info.InfluenceWeights[al] = Weight[i].InfluenceWeights[al];
}
InWeights[i] = info;
}
pRd->ReleaseResources();
pRd->SkinWeightVertexBuffer = InWeights;
#if UE_VERSION_OLDER_THAN(5,4,0)
pRd->InitResources(false, 0, VRMGetMorphTargets(sk), sk);
#else
{
TArray<UMorphTarget*> dummy;
for (auto& a : VRMGetMorphTargets(sk)) {
dummy.Add(a);
}
#if UE_VERSION_OLDER_THAN(5,7,0)
pRd->InitResources(false, 0, dummy, sk);
#else
pRd->InitResources(false, 0, sk);
#endif
}
#endif // 5.4
}
#endif // 4.25
#endif // editor
//rd.StaticVertexBuffers.StaticMeshVertexBuffer.TexcoordDataPtr;
if (VRMConverter::IsImportMode() == false) {
ENQUEUE_RENDER_COMMAND(UpdateCommand)(
[sk, Triangles, Weight](FRHICommandListImmediate& RHICmdList)
{
#if UE_VERSION_OLDER_THAN(5,3,0)
#define VRM4U_RHI_CMD_LIST
#else
#define VRM4U_RHI_CMD_LIST RHICmdList
#endif
FSkeletalMeshLODRenderData &d = sk->GetResourceForRendering()->LODRenderData[0];
if (d.MultiSizeIndexContainer.IsIndexBufferValid()) {
d.MultiSizeIndexContainer.GetIndexBuffer()->ReleaseResource();
}
d.MultiSizeIndexContainer.RebuildIndexBuffer(sizeof(uint32), Triangles);
d.MultiSizeIndexContainer.GetIndexBuffer()->InitResource(VRM4U_RHI_CMD_LIST);
//d.AdjacencyMultiSizeIndexContainer.CopyIndexBuffer(Triangles);
#if UE_VERSION_OLDER_THAN(5,0,0)
if (d.AdjacencyMultiSizeIndexContainer.IsIndexBufferValid()) {
d.AdjacencyMultiSizeIndexContainer.GetIndexBuffer()->ReleaseResource();
}
d.AdjacencyMultiSizeIndexContainer.RebuildIndexBuffer(sizeof(uint32), Triangles);
d.AdjacencyMultiSizeIndexContainer.GetIndexBuffer()->InitResource();
#endif
#if WITH_EDITOR
d.SkinWeightVertexBuffer.Init(Weight);
#else
{
#if UE_VERSION_OLDER_THAN(4,25,0)
typedef TSkinWeightInfo<false> WeightInfo;
#else
typedef FSkinWeightInfo WeightInfo;
#endif
TArray< WeightInfo > InWeights;
InWeights.Reserve(Weight.Num());
int elem = 0;
{
if (Weight.Num()) {
elem = std::extent<decltype(Weight[0].InfluenceBones)>::value;
}
WeightInfo info = {};
elem = FMath::Min(elem, (int)std::extent<decltype(info.InfluenceBones)>::value);
};
for (int i = 0; i < Weight.Num(); ++i) {
auto n = new(InWeights) WeightInfo;
WeightInfo info = {};
for (int al = 0; al < elem; ++al) {
// uint8 <> uint16
info.InfluenceBones[al] = Weight[i].InfluenceBones[al];
info.InfluenceWeights[al] = Weight[i].InfluenceWeights[al];
}
*n = info;
}
d.SkinWeightVertexBuffer = InWeights;
}
#endif // editor
#if UE_VERSION_OLDER_THAN(4,25,0)
d.SkinWeightVertexBuffer.InitResource();
#else
#endif
d.StaticVertexBuffers.PositionVertexBuffer.UpdateRHI(VRM4U_RHI_CMD_LIST);
d.StaticVertexBuffers.StaticMeshVertexBuffer.UpdateRHI(VRM4U_RHI_CMD_LIST);
d.MultiSizeIndexContainer.GetIndexBuffer()->UpdateRHI(VRM4U_RHI_CMD_LIST);
#if UE_VERSION_OLDER_THAN(5,0,0)
d.AdjacencyMultiSizeIndexContainer.GetIndexBuffer()->UpdateRHI();
#endif
#if UE_VERSION_OLDER_THAN(4,25,0)
d.SkinWeightVertexBuffer.UpdateRHI();
#else
#endif
});
}
}
{
FBox BoundingBox(BoundMin, BoundMax);
sk->SetImportedBounds(FBoxSphereBounds(BoundingBox));
}
{
#if WITH_EDITOR
{
sk->GetImportedModel()->LODModels[0].NumTexCoords = uvNum;
}
if (VRMConverter::IsImportMode()) {
sk->UpdateUVChannelData(false);
}
#endif
}
#if WITH_EDITOR
if (VRMConverter::IsImportMode()) {
#if UE_VERSION_OLDER_THAN(4,25,0)
UProperty* ChangedProperty = FindField<UProperty>(USkeletalMesh::StaticClass(), "Materials");
check(ChangedProperty);
sk->PreEditChange(ChangedProperty);
FPropertyChangedEvent PropertyUpdateStruct(ChangedProperty);
sk->PostEditChangeProperty(PropertyUpdateStruct);
sk->PostEditChange();
#else
FProperty* ChangedProperty = FindFProperty<FProperty>(USkeletalMesh::StaticClass(), TEXT("Materials"));
check(ChangedProperty);
sk->PreEditChange(ChangedProperty);
FPropertyChangedEvent PropertyUpdateStruct(ChangedProperty);
sk->PostEditChangeProperty(PropertyUpdateStruct);
sk->PostEditChange();
#endif
}
#endif
#if WITH_EDITOR
{
//FSkeletalMeshLODRenderData& rd = sk->GetResourceForRendering()->LODRenderData[0];
{
FSkeletalMeshLODModel* p = &(sk->GetImportedModel()->LODModels[0]);
if (Options::Get().IsActiveBone()) {
auto& r = VRMGetSkeleton(sk)->GetReferenceSkeleton();
for (int i = 0; i < AllActiveBones.Num(); ++i) {
auto boneIndex = r.GetParentIndex(AllActiveBones[i]);
if (boneIndex >= 0) {
AllActiveBones.AddUnique(boneIndex);
}
}
AllActiveBones.Sort();
p->ActiveBoneIndices.SetNum(AllActiveBones.Num());
for (int i = 0; i < AllActiveBones.Num(); ++i) {
p->ActiveBoneIndices[i] = AllActiveBones[i];
}
} else {
int boneNum = VRMGetSkeleton(sk)->GetReferenceSkeleton().GetRawBoneNum();
p->ActiveBoneIndices.SetNum(boneNum);
for (int i = 0; i < boneNum; ++i) {
p->ActiveBoneIndices[i] = i;
}
}
}
{
//p->NumVertices = allVertex;
//p->NumTexCoords = uvNum; // allVertex;
//p->IndexBuffer = Triangles;
//p->RequiredBones = rd.RequiredBones;
}
}
#endif
}
UPhysicsAsset *pa = nullptr;
{
// remove
if (vrmAssetList->Package == GetTransientPackage()) {
} else {
TArray<UObject*> ret;
FString name = (TEXT("PHYS_") + vrmAssetList->BaseFileName);
GetObjectsWithOuter(vrmAssetList->Package, ret);
for (auto *a : ret) {
auto s = a->GetName().ToLower();
if (s.IsEmpty()) continue;
if (s == name.ToLower()) {
a->ClearFlags(EObjectFlags::RF_Standalone);
a->SetFlags(EObjectFlags::RF_Public | RF_Transient);
a->ConditionalBeginDestroy();
break;
}
}
}
}
if (aiData->mVRMMeta && Options::Get().IsSkipPhysics()==false) {
bool spring0 = false;
bool spring1 = false;
VRM::VRMMetadata* meta = reinterpret_cast<VRM::VRMMetadata*>(aiData->mVRMMeta);
// vrm0
if (meta->springNum > 0) {
spring0 = true;
}
if (vrmAssetList) {
if (vrmAssetList->VrmMetaObject) {
if (vrmAssetList->VrmMetaObject->VRM1SpringBoneMeta.Colliders.Num() > 0) {
spring1 = true;
}
}
}
if (spring0 || spring1) {
if (vrmAssetList->Package == GetTransientPackage()) {
pa = VRM4U_NewObject<UPhysicsAsset>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient, NULL);
}
else {
FString name = (TEXT("PHYS_") + vrmAssetList->BaseFileName);
pa = VRM4U_NewObject<UPhysicsAsset>(vrmAssetList->Package, *name, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
pa->Modify();
#if WITH_EDITORONLY_DATA
pa->SetPreviewMesh(sk);
#endif
VRMSetPhysicsAsset(sk, pa);
addedList.Empty();
}
// vrm0
if (spring0) {
TArray<int> swingBoneIndexArray;
for (int i = 0; i < meta->springNum; ++i) {
auto& spring = meta->springs[i];
for (int j = 0; j < spring.boneNum; ++j) {
auto& sbone = spring.bones_name[j];
FString s = UTF8_TO_TCHAR(sbone.C_Str());
int sboneIndex = VRMGetRefSkeleton(sk).FindRawBoneIndex(*s);
if (sboneIndex == INDEX_NONE) {
continue;
}
FName parentName = *s;
CreateSwingHead(vrmAssetList, spring, parentName, swingBoneIndexArray, sboneIndex);
}
} // all spring
{
swingBoneIndexArray.Sort();
for (int i = 0; i < swingBoneIndexArray.Num() - 1; ++i) {
for (int j = i + 1; j < swingBoneIndexArray.Num(); ++j) {
pa->DisableCollision(swingBoneIndexArray[i], swingBoneIndexArray[j]);
}
}
}
// collision
for (int i = 0; i < meta->colliderGroupNum; ++i) {
auto& c = meta->colliderGroups[i];
USkeletalBodySetup* bs = nullptr;
{
FString s = UTF8_TO_TCHAR(c.node_name.C_Str());
{
int sboneIndex = VRMGetRefSkeleton(sk).FindRawBoneIndex(*s);
if (sboneIndex == INDEX_NONE) {
continue;
}
}
s = s.ToLower();
// addlist changed recur...
if (1) {//addedList.Find(s) >= 0) {
for (auto& a : pa->SkeletalBodySetups) {
if (a->BoneName.IsEqual(*s)) {
bs = a;
break;
}
}
}
else {
addedList.Add(s);
}
}
if (bs == nullptr) {
bs = NewObject<USkeletalBodySetup>(pa, NAME_None, RF_Transactional);
bs->InvalidatePhysicsData();
pa->SkeletalBodySetups.Add(bs);
}
FKAggregateGeom agg;
for (int j = 0; j < c.colliderNum; ++j) {
FKSphereElem SphereElem;
VRM::vec3 v = {
c.colliders[j].offset[0] * 100.f,
c.colliders[j].offset[1] * 100.f,
c.colliders[j].offset[2] * 100.f,
};
SphereElem.Center = FVector(-v[0], v[2], v[1]);
SphereElem.Radius = c.colliders[j].radius * 100.f;
agg.SphereElems.Add(SphereElem);
}
bs->Modify();
bs->BoneName = UTF8_TO_TCHAR(c.node_name.C_Str());
bs->AddCollisionFrom(agg);
bs->CollisionTraceFlag = CTF_UseSimpleAsComplex;
// newly created bodies default to simulating
bs->PhysicsType = PhysType_Kinematic; // fix!
//bs.constra
//sk->SkeletalBodySetups.Num();
bs->CreatePhysicsMeshes();
}// collision
}// vrm0
if (spring1){
auto& sm = vrmAssetList->VrmMetaObject->VRM1SpringBoneMeta;
TArray<int> swingBoneIndexArray;
for (int i = 0; i < sm.Springs.Num(); ++i) {
auto& spring = sm.Springs[i];
for (int j = 0; j < spring.joints.Num(); ++j) {
auto& sbone = spring.joints[j].boneName;
FString s = sbone;
int sboneIndex = VRMGetRefSkeleton(sk).FindRawBoneIndex(*s);
if (sboneIndex == INDEX_NONE) {
continue;
}
FName parentName = *s;
//CreateSwingHead(vrmAssetList, spring, parentName, swingBoneIndexArray, sboneIndex);
}
} // all spring
{
swingBoneIndexArray.Sort();
for (int i = 0; i < swingBoneIndexArray.Num() - 1; ++i) {
for (int j = i + 1; j < swingBoneIndexArray.Num(); ++j) {
pa->DisableCollision(swingBoneIndexArray[i], swingBoneIndexArray[j]);
}
}
}
// collision
for (int i = 0; i < sm.ColliderGroups.Num(); ++i) {
auto& c = sm.ColliderGroups[i];
USkeletalBodySetup* bs = nullptr;
/*
{
FString s = c.name;
{
int sboneIndex = VRMGetRefSkeleton(sk).FindRawBoneIndex(*s);
if (sboneIndex == INDEX_NONE) {
continue;
}
}
s = s.ToLower();
// addlist changed recur...
if (1) {//addedList.Find(s) >= 0) {
for (auto& a : pa->SkeletalBodySetups) {
if (a->BoneName.IsEqual(*s)) {
bs = a;
break;
}
}
}
else {
addedList.Add(s);
}
}
*/
for (int j = 0; j < c.colliders.Num(); ++j) {
FKAggregateGeom agg;
int ind = c.colliders[j];
auto& col = sm.Colliders[ind];
// sb setup
{
FString s = col.boneName;
{
int sboneIndex = VRMGetRefSkeleton(sk).FindRawBoneIndex(*s);
if (sboneIndex == INDEX_NONE) {
continue;
}
}
s = s.ToLower();
// addlist changed recur...
if (1) {//addedList.Find(s) >= 0) {
for (auto& a : pa->SkeletalBodySetups) {
if (a->BoneName.IsEqual(*s)) {
bs = a;
break;
}
}
}
else {
addedList.Add(s);
}
}
if (bs == nullptr) {
bs = NewObject<USkeletalBodySetup>(pa, NAME_None, RF_Transactional);
bs->InvalidatePhysicsData();
pa->SkeletalBodySetups.Add(bs);
}
if (col.shapeType == TEXT("sphere")) {
VRM::vec3 v = {
col.offset[0] * 100,
col.offset[1] * 100,
col.offset[2] * 100
};
FKSphereElem SphereElem;
SphereElem.Center = FVector(v[0], -v[2], v[1]);
SphereElem.Radius = col.radius * 100.f;
agg.SphereElems.Add(SphereElem);
}
else {
VRM::vec3 v0 = {
col.offset[0] * 100,
col.offset[1] * 100,
col.offset[2] * 100
};
VRM::vec3 v1 = {
col.tail[0] * 100,
col.tail[1] * 100,
col.tail[2] * 100
};
VRM::vec3 v = {
v0[0] + v1[0] / 2,
v0[1] + v1[1] / 2,
v0[2] + v1[2] / 2,
};
float len =
FMath::Sqrt(
FMath::Square(v0[0] - v1[0])
+ FMath::Square(v0[1] - v1[1])
+ FMath::Square(v0[2] - v1[2])
);
FKSphylElem s;
s.Center = FVector(v[0], -v[2], v[1]);
s.Radius = col.radius * 100.f;
s.Length = len;
agg.SphylElems.Add(s);
}
bs->Modify();
bs->BoneName = *col.boneName;
bs->AddCollisionFrom(agg);
bs->CollisionTraceFlag = CTF_UseSimpleAsComplex;
// newly created bodies default to simulating
bs->PhysicsType = PhysType_Kinematic; // fix!
//bs.constra
//sk->SkeletalBodySetups.Num();
bs->CreatePhysicsMeshes();
}// collision
}// collision group
}
if (pa){
pa->UpdateBodySetupIndexMap();
RefreshSkelMeshOnPhysicsAssetChange(sk);
#if WITH_EDITOR
pa->RefreshPhysicsAssetChange();
#endif
pa->UpdateBoundsBodiesArray();
#if WITH_EDITOR
if (VRMConverter::IsImportMode()) {
pa->PostEditChange();
}
#endif
}
//FSkeletalMeshModel* ImportedResource = sk->GetImportedModel();
//if (ImportedResource->LODModels.Num() == 0)
}
//NewAsset->FindSocket
#if WITH_EDITOR
// animation
if (aiData->mNumAnimations > 0) {
UAnimSequence* ase;
ase = VRM4U_NewObject<UAnimSequence>(vrmAssetList->Package, *(TEXT("A_") + vrmAssetList->BaseFileName), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
ase->SetSkeleton(k);
#if UE_VERSION_OLDER_THAN(5,0,0)
ase->CleanAnimSequenceForImport();
#else
int FrameNum = 0;
for (uint32_t animNo = 0; animNo < aiData->mNumAnimations; animNo++) {
aiAnimation* aiA = aiData->mAnimations[animNo];
for (uint32_t chanNo = 0; chanNo < aiA->mNumChannels; chanNo++) {
aiNodeAnim* aiNA = aiA->mChannels[chanNo];
FrameNum = FMath::Max(FrameNum, (int)aiNA->mNumPositionKeys);
FrameNum = FMath::Max(FrameNum, (int)aiNA->mNumRotationKeys);
}
}
auto& Controller = ase->GetController();
#if UE_VERSION_OLDER_THAN(5,2,0)
#else
Controller.OpenBracket(LOCTEXT("VRM4U", "Importing BVH"), false);
#endif
Controller.ResetModel();
double AnimDeltaTime = 0.f;
{
for (uint32_t animNo = 0; animNo < aiData->mNumAnimations; animNo++) {
aiAnimation* aiA = aiData->mAnimations[animNo];
for (uint32_t chanNo = 0; chanNo < aiA->mNumChannels; chanNo++) {
aiNodeAnim* aiNA = aiA->mChannels[chanNo];
for (int i = 0; i < (int)(aiNA->mNumRotationKeys)-1; ++i) {
auto dif = (aiNA->mRotationKeys[i+1].mTime - aiNA->mRotationKeys[i].mTime) / aiA->mTicksPerSecond;
if (AnimDeltaTime == 0.f && dif != 0.f) {
AnimDeltaTime = dif;
}
}
}
}
}
#if UE_VERSION_OLDER_THAN(5,2,0)
Controller.SetPlayLength(FrameNum-1);
Controller.SetFrameRate(FFrameRate(1, 1));
#else
Controller.InitializeModel();
ase->ResetAnimation();
ase->SetPreviewMesh(sk);
if (AnimDeltaTime > 0.f) {
Controller.SetFrameRate(FFrameRate(1.f / AnimDeltaTime, 1));
} else {
Controller.SetFrameRate(FCommonFrameRates::FPS_30());
}
Controller.SetNumberOfFrames(FrameNum - 1);
#endif
#if UE_VERSION_OLDER_THAN(5,3,0)
Controller.UpdateCurveNamesFromSkeleton(k, ERawCurveTrackTypes::RCT_Float);
#endif
//Controller.OpenBracket(LOCTEXT("VRM4U", "Importing BVH"), false);
ase->RateScale = VRMConverter::Options::Get().GetAnimationPlayRateScale();
#endif
float totalTime = 0.f;
int totalFrameNum = 0;
if (1){
//TArray<AnimationTransformDebug::FAnimationTransformDebugData> TransformDebugData;
for (uint32_t animNo = 0; animNo < aiData->mNumAnimations; animNo++) {
aiAnimation* aiA = aiData->mAnimations[animNo];
for (uint32_t chanNo = 0; chanNo < aiA->mNumChannels; chanNo++) {
aiNodeAnim* aiNA = aiA->mChannels[chanNo];
FRawAnimSequenceTrack RawTrack;
bool isRootBone = false;
FName NodeName = UTF8_TO_TCHAR(aiNA->mNodeName.C_Str());
if (VRMConverter::Options::Get().IsVRMAModel()) {
if (vrmAssetList->VrmMetaObject) {
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
if (t.Value == NodeName.ToString()) {
NodeName = *t.Key;
break;
}
}
}
}
{
auto ind = k->GetReferenceSkeleton().FindBoneIndex(NodeName);
if (ind != INDEX_NONE) {
ind = k->GetReferenceSkeleton().GetParentIndex(ind);
if (ind == INDEX_NONE) {
// root bone. no parent.
isRootBone = true;
}
}
}
{
for (uint32_t i = 0; i < aiNA->mNumPositionKeys; ++i) {
const auto &v = aiNA->mPositionKeys[i].mValue;
//FVector pos(v.x, v.y, v.z);
float Scale = 1.f;
if (VRMConverter::Options::Get().IsVRMAModel()) {
Scale = 100.f;
}
FVector pos(-v.x, v.z, v.y);
pos *= Scale * VRMConverter::Options::Get().GetAnimationTranslateScale();
if (VRMConverter::Options::Get().IsVRM10Model()) {
pos.Set(v.x, -v.z, v.y);
pos *= Scale * VRMConverter::Options::Get().GetAnimationTranslateScale();
}
if (VRMConverter::Options::Get().IsPMXModel() || VRMConverter::Options::Get().IsBVHModel()) {
pos.X *= -1.f;
pos.Y *= -1.f;
}
//if (VRMConverter::Options::Get().IsVRMAModel() ) {
// if (isRootBone) {
// pos.X *= -1.f;
// pos.Y *= -1.f;
// }else {
// pos.Set(v.x, v.y, v.z);
// pos *= Scale * VRMConverter::Options::Get().GetAnimationTranslateScale();
// }
//}
#if UE_VERSION_OLDER_THAN(5,0,0)
RawTrack.PosKeys.Add(pos);
#else
RawTrack.PosKeys.Add(FVector3f(pos));
#endif
totalTime = FMath::Max((float)aiNA->mPositionKeys[i].mTime, totalTime);
}
if (chanNo == 0) {
if (RawTrack.PosKeys.Num()) {
//ase->bEnableRootMotion = true;
}
}
}
{
for (uint32_t i = 0; i < aiNA->mNumRotationKeys; ++i) {
const auto &v = aiNA->mRotationKeys[i].mValue;
#if UE_VERSION_OLDER_THAN(5,0,0)
FQuat q(v.x, v.y, v.z, v.w);
#else
FQuat4f q(v.x, v.y, v.z, v.w);
#endif
if (1) {
#if UE_VERSION_OLDER_THAN(5,0,0)
q = FQuat(-v.x, v.y, v.z, -v.w);
{
FQuat d = FQuat(FVector(1, 0, 0), -PI / 2.f);
q = d * q * d.Inverse();
}
#else
q = FQuat4f(-v.x, v.y, v.z, -v.w);
if (VRMConverter::Options::Get().IsBVHModel()) {
auto d = FQuat4f(FVector3f(1, 0, 0), -PI / 2.f);
q = d * q * d.Inverse();
}
if (VRMConverter::Options::Get().IsVRM10Model()) {
q = FQuat4f(v.x, -v.z, v.y, v.w);
}
#endif
}
//FQuat q(v.x, v.y, v.z, v.w);
//FVector a = q.GetRotationAxis();
//q = FQuat(FVector(0, 0, 1), PI) * q;
//FMatrix m = q.GetRotationAxis
//q = FRotator(0, 90, 0).Quaternion() * q;
RawTrack.RotKeys.Add(q);
totalTime = FMath::Max((float)aiNA->mRotationKeys[i].mTime, totalTime);
}
for (uint32_t i = 0; i < aiNA->mNumScalingKeys; ++i) {
const auto &v = aiNA->mScalingKeys[i].mValue;
FVector s(v.x, v.y, v.z);
#if UE_VERSION_OLDER_THAN(5,0,0)
RawTrack.ScaleKeys.Add(s);
#else
RawTrack.ScaleKeys.Add(FVector3f(s));
#endif
totalTime = FMath::Max((float)aiNA->mScalingKeys[i].mTime, totalTime);
}
}
if (RawTrack.RotKeys.Num() || RawTrack.PosKeys.Num() || RawTrack.ScaleKeys.Num()) {
#if UE_VERSION_OLDER_THAN(5,0,0)
if (RawTrack.PosKeys.Num() == 0) {
RawTrack.PosKeys.Add(FVector::ZeroVector);
}
if (RawTrack.RotKeys.Num() == 0) {
RawTrack.RotKeys.Add(FQuat::Identity);
}
if (RawTrack.ScaleKeys.Num() == 0) {
RawTrack.ScaleKeys.Add(FVector::OneVector);
}
#else
while (RawTrack.PosKeys.Num() < FrameNum) {
RawTrack.PosKeys.Add(FVector3f::ZeroVector);
}
while (RawTrack.RotKeys.Num() < FrameNum) {
RawTrack.RotKeys.Add(FQuat4f::Identity);
}
while (RawTrack.ScaleKeys.Num() < FrameNum) {
RawTrack.ScaleKeys.Add(FVector3f::OneVector);
}
#endif
#if UE_VERSION_OLDER_THAN(5,0,0)
int32 NewTrackIdx = ase->AddNewRawTrack(UTF8_TO_TCHAR(aiNA->mNodeName.C_Str()), &RawTrack);
#elif UE_VERSION_OLDER_THAN(5,2,0)
if (Controller.AddBoneTrack(UTF8_TO_TCHAR(aiNA->mNodeName.C_Str())) != INDEX_NONE) {
Controller.SetBoneTrackKeys(UTF8_TO_TCHAR(aiNA->mNodeName.C_Str()), RawTrack.PosKeys, RawTrack.RotKeys, RawTrack.ScaleKeys);
}
#else
{
if (VRMConverter::Options::Get().IsVRMAModel()) {
auto &presetList = vrmAssetList->VrmMetaObject->VRMAnimationMeta.expressionPreset;
for (auto& p : presetList) {
if (NodeName != *p.expressionNodeName) continue;
FFloatCurve c;
c.SetCurveTypeFlag(AACF_Editable, true);
auto boneNo = k->GetReferenceSkeleton().FindBoneIndex(*p.expressionNodeName);
if (boneNo != INDEX_NONE) {
for (int i = 0; i < RawTrack.PosKeys.Num(); ++i) {
c.UpdateOrAddKey(RawTrack.PosKeys[i].X / 100.f, i * AnimDeltaTime);
}
}
#if UE_VERSION_OLDER_THAN(5,3,0)
FSmartName sm;
{
TArray<FName> a = {*p.expressionName };
k->RemoveSmartnamesAndModify(USkeleton::AnimCurveMappingName, a);
}
k->AddSmartNameAndModify(USkeleton::AnimCurveMappingName, *p.expressionName, sm);
FAnimationCurveIdentifier f(sm, ERawCurveTrackTypes::RCT_Float);
#else
FAnimationCurveIdentifier f(*p.expressionName, ERawCurveTrackTypes::RCT_Float);
#endif
FName name;
Controller.AddCurve(f);
Controller.SetCurveKeys(f, c.FloatCurve.GetConstRefOfKeys());
}
}
if (Controller.AddBoneCurve(NodeName)) {
Controller.SetBoneTrackKeys(NodeName, RawTrack.PosKeys, RawTrack.RotKeys, RawTrack.ScaleKeys);
}
}
#endif
}
totalFrameNum = FMath::Max(totalFrameNum, RawTrack.RotKeys.Num());
totalFrameNum = FMath::Max(totalFrameNum, RawTrack.PosKeys.Num());
totalTime = totalFrameNum / aiA->mTicksPerSecond;
#if UE_VERSION_OLDER_THAN(4,22,0)
ase->NumFrames = totalFrameNum;
#elif UE_VERSION_OLDER_THAN(5,0,0)
ase->SetRawNumberOfFrame(totalFrameNum);
#else
#endif
}
}
}
{
#if UE_VERSION_OLDER_THAN(5,0,0)
ase->SequenceLength = totalTime;
const bool bSourceDataExists = ase->HasSourceRawData();
if (bSourceDataExists)
{
ase->BakeTrackCurvesToRawAnimation();
} else {
ase->PostProcessSequence();
}
#else
Controller.NotifyPopulated();
Controller.CloseBracket(true);
#endif
#if UE_VERSION_OLDER_THAN(5,2,0)
ase->MarkRawDataAsModified();
#else
#endif
}
ase->PostEditChange();
}
{
FSoftObjectPath r(TEXT("/VRM4U/Util/Actor/latest/ABP_PostProcessBase.ABP_PostProcessBase"));
UObject* u = r.TryLoad();
if (u) {
FString name = FString(TEXT("ABP_Post_")) + vrmAssetList->BaseFileName;
auto b = Cast<UAnimBlueprint>(VRM4U_StaticDuplicateObject(u, vrmAssetList->Package, *name, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone));
if (b) {
b->MarkPackageDirty();
b->TargetSkeleton = k;
b->SetPreviewMesh(sk);
#if WITH_EDITOR
FKismetEditorUtilities::CompileBlueprint(b);
#endif
UBlueprintGeneratedClass* bpClass = Cast<UBlueprintGeneratedClass>(b->GeneratedClass);
#if UE_VERSION_OLDER_THAN(4,27,0)
sk->PostProcessAnimBlueprint = bpClass;
#else
sk->SetPostProcessAnimBlueprint(bpClass);
#endif
sk->PostEditChange();
}
}
}
#endif
return true;
}
VrmConvertModel::VrmConvertModel()
{
}
VrmConvertModel::~VrmConvertModel()
{
}