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

501 lines
13 KiB
C++

// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvertMorphTarget.h"
#include "VrmConvert.h"
#include "VrmAssetListObject.h"
#include "LoaderBPFunctionLibrary.h"
#include "VRM4ULoaderLog.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 "PhysicsEngine/PhysicsAsset.h"
#include "PhysicsEngine/PhysicsConstraintTemplate.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>
namespace {
#if WITH_EDITOR
void LocalPopulateDeltas(UMorphTarget* Morph, const TArray<FMorphTargetDelta>& Deltas, const int32 LODIndex, const TArray<struct FSkelMeshSection>& Sections)
{
Morph->PopulateDeltas(Deltas, LODIndex, Sections);
}
#else
void LocalPopulateDeltas(USkeletalMesh* sk, UMorphTarget * Morph, const TArray<FMorphTargetDelta> & Deltas, const int32 LODIndex, const bool bCompareNormal = false, const bool bGeneratedByReductionSetting = false, const float PositionThreshold = THRESH_POINTS_ARE_NEAR)
{
//TIndirectArray<FSkeletalMeshLODRenderData> LODRenderData;
const auto& Sections = sk->GetResourceForRendering()->LODRenderData[0].RenderSections;
//FSkeletalMeshLODRenderData& rd = sk->GetResourceForRendering()->LODRenderData[0];
#if UE_VERSION_OLDER_THAN(5,0,0)
auto& MorphLODModels = Morph->MorphLODModels;
#else
auto& MorphLODModels = Morph->GetMorphLODModels();
#endif
// create the LOD entry if it doesn't already exist
if (LODIndex >= MorphLODModels.Num())
{
MorphLODModels.AddDefaulted(LODIndex - MorphLODModels.Num() + 1);
}
// morph mesh data to modify
FMorphTargetLODModel& MorphModel = MorphLODModels[LODIndex];
// copy the wedge point indices
// for now just keep every thing
// set the original number of vertices
MorphModel.NumBaseMeshVerts = Deltas.Num();
// empty morph mesh vertices first
MorphModel.Vertices.Empty(Deltas.Num());
// mark if generated by reduction setting, so that we can remove them later if we want to
// we don't want to delete if it has been imported
MorphModel.bGeneratedByEngine = bGeneratedByReductionSetting;
// Still keep this (could remove in long term due to incoming data)
for (const FMorphTargetDelta& Delta : Deltas)
{
if (Delta.PositionDelta.SizeSquared() > FMath::Square(PositionThreshold) ||
(bCompareNormal && Delta.TangentZDelta.SizeSquared() > 0.01f))
{
MorphModel.Vertices.Add(Delta);
for (int32 SectionIdx = 0; SectionIdx < Sections.Num(); ++SectionIdx)
{
if (MorphModel.SectionIndices.Contains(SectionIdx))
{
continue;
}
//Sections[0].BaseVertexIndex
//const uint32 BaseVertexBufferIndex = (uint32)(Sections[SectionIdx].GetVertexBufferIndex());
//const uint32 LastVertexBufferIndex = (uint32)(BaseVertexBufferIndex + Sections[SectionIdx].GetNumVertices());
#if UE_VERSION_OLDER_THAN(5,0,0)
const uint32 BaseVertexBufferIndex = (uint32)(Sections[SectionIdx].BaseVertexIndex);
const uint32 LastVertexBufferIndex = (uint32)(BaseVertexBufferIndex + Sections[SectionIdx].NumVertices);
#else
const uint32 BaseVertexBufferIndex = (uint32)(Sections[SectionIdx].GetVertexBufferIndex());
const uint32 LastVertexBufferIndex = (uint32)(BaseVertexBufferIndex + Sections[SectionIdx].GetNumVertices());
#endif
if (BaseVertexBufferIndex <= Delta.SourceIdx && Delta.SourceIdx < LastVertexBufferIndex)
{
MorphModel.SectionIndices.AddUnique(SectionIdx);
break;
}
}
}
}
// sort the array of vertices for this morph target based on the base mesh indices
// that each vertex is associated with. This allows us to sequentially traverse the list
// when applying the morph blends to each vertex.
struct FCompareMorphTargetDeltas
{
FORCEINLINE bool operator()(const FMorphTargetDelta& A, const FMorphTargetDelta& B) const
{
return ((int32)A.SourceIdx - (int32)B.SourceIdx) < 0 ? true : false;
}
};
MorphModel.Vertices.Sort(FCompareMorphTargetDeltas());
// remove array slack
MorphModel.Vertices.Shrink();
#if UE_VERSION_OLDER_THAN(5,0,0)
#else
MorphModel.NumVertices = MorphModel.Vertices.Num();
#endif
}
#endif
}
static bool readMorph2(TArray<FMorphTargetDelta> &MorphDeltas, aiString targetName,const aiScene *aiData, const UVrmAssetListObject *assetList) {
MorphDeltas.Reset(0);
uint32_t currentVertex = 0;
FMorphTargetDelta morphinit;
#if UE_VERSION_OLDER_THAN(5,0,0)
morphinit.PositionDelta = FVector::ZeroVector;
morphinit.SourceIdx = 0;
morphinit.TangentZDelta = FVector::ZeroVector;
#else
morphinit.PositionDelta = FVector3f::ZeroVector;
morphinit.SourceIdx = 0;
morphinit.TangentZDelta = FVector3f::ZeroVector;
#endif
for (uint32_t m = 0; m < aiData->mNumMeshes; ++m) {
const auto &mesh = assetList->MeshReturnedData->meshInfo[m];
const aiMesh &aiM = *(aiData->mMeshes[m]);
for (uint32_t a = 0; a < aiM.mNumAnimMeshes; ++a) {
const aiAnimMesh &aiA = *(aiM.mAnimMeshes[a]);
if (targetName != aiA.mName) {
continue;
}
if (aiM.mNumVertices != aiA.mNumVertices) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("test18.\n"));
}
TArray<FMorphTargetDelta> tmpData;
tmpData.Reserve(aiA.mNumVertices);
bool bIncludeNormal = VRMConverter::Options::Get().IsEnableMorphTargetNormal();
int VertexCount = 0;
for (uint32_t i = 0; i < aiA.mNumVertices; ++i) {
if (mesh.vertexUseFlag.Num() > 0) {
if (mesh.vertexUseFlag[i] == false) {
continue;
}
}
#if UE_VERSION_OLDER_THAN(5,0,0)
FMorphTargetDelta v = { FVector::ZeroVector, FVector::ZeroVector, 0 };
#else
FMorphTargetDelta v = { FVector3f::ZeroVector, FVector3f::ZeroVector, 0 };
#endif
v.SourceIdx = VertexCount + currentVertex;
++VertexCount;
if (aiA.mVertices) {
auto aiV = aiA.mVertices[i] - aiM.mVertices[i];
v.PositionDelta.Set(
-aiV[0] * 100.f,
aiV[2] * 100.f,
aiV[1] * 100.f
);
if (VRMConverter::Options::Get().IsVRM10Model()) {
v.PositionDelta.Set(
aiV[0] * 100.f,
-aiV[2] * 100.f,
aiV[1] * 100.f
);
}
// apply original root bone rotation
FVector tmp;
tmp.Set(v.PositionDelta.X, v.PositionDelta.Y, v.PositionDelta.Z);
tmp = assetList->model_root_transform.TransformVector(tmp);
v.PositionDelta.Set(tmp.X, tmp.Y, tmp.Z);
}
v.PositionDelta *= VRMConverter::Options::Get().GetModelScale();
if (bIncludeNormal) {
auto aiV = aiA.mNormals[i] - aiM.mNormals[i];
#if UE_VERSION_OLDER_THAN(5,0,0)
FVector n(
#else
FVector3f n(
#endif
-aiV[0],
aiV[2],
aiV[1]);
if (VRMConverter::Options::Get().IsVRM10Model()) {
n.Set(
aiV[0],
-aiV[2],
aiV[1]);
}
if (n.Size() > 1.f) {
v.TangentZDelta = n.GetUnsafeNormal();
}
if (VRMConverter::Options::Get().IsVRM10Model()) {
v.TangentZDelta.X *= -1.f;
v.TangentZDelta.Y *= -1.f;
}
}
// skip invalid vertex data
if (v.PositionDelta.Size() == 0) {
if (bIncludeNormal == false) {
continue;
}
if (v.TangentZDelta.Size() == 0) {
continue;
}
}
tmpData.Add(v);
} // vertex loop
MorphDeltas.Append(tmpData);
}
if (mesh.vertexUseFlag.Num() > 0) {
currentVertex += mesh.useVertexCount;
}else{
currentVertex += aiM.mNumVertices;
}
}
return MorphDeltas.Num() != 0;
}
bool VRMConverter::ConvertMorphTarget(UVrmAssetListObject *vrmAssetList) {
if (Options::Get().IsSkipMorphTarget()) {
return true;
}
if (vrmAssetList->SkeletalMesh == nullptr) {
return false;
}
USkeletalMesh *sk = vrmAssetList->SkeletalMesh;
TArray<FString> MorphNameList;
TArray<FString> MorphNameList_Strict;
TMap<FString, FString> MorphName_Orig_to_Name;
TArray<UMorphTarget*> MorphTargetList;
for (uint32_t m = 0; m < aiData->mNumMeshes; ++m) {
const aiMesh &aiM = *(aiData->mMeshes[m]);
for (uint32_t a = 0; a < aiM.mNumAnimMeshes; ++a) {
const aiAnimMesh &aiA = *(aiM.mAnimMeshes[a]);
//aiA.
TArray<FMorphTargetDelta> MorphDeltas;
FString morphName = UTF8_TO_TCHAR(aiA.mName.C_Str());
FString morphNameOrg = morphName;
if (VRMConverter::Options::Get().IsForceOriginalMorphTargetName()) {
}else{
auto mName = MorphName_Orig_to_Name.Find(morphNameOrg);
if (mName) {
continue;
//morphName = *mName;
} else {
morphName = VRMUtil::MakeName(morphName);
bool isInvalidName = VRMUtil::IsNoSafeName(morphName);
auto tmp = morphName;
int i = 0;
while (MorphNameList.Find(tmp) != INDEX_NONE || isInvalidName) {
++i;
isInvalidName = false;
tmp = TEXT("UE5EA_patch_") + morphName + TEXT("_") + FString::FromInt(i);
}
morphName = tmp;
MorphName_Orig_to_Name.Add(morphNameOrg, morphName);
}
}
if (morphName == TEXT("")) {
//morphName = FString::Printf("%d_%d", m, a);
}
if (MorphNameList.Find(morphName) != INDEX_NONE) {
// continue;
}
MorphNameList.AddUnique(morphName);
MorphNameList_Strict.AddUnique(morphNameOrg);
if (readMorph2(MorphDeltas, aiA.mName, aiData, vrmAssetList) == false) {
continue;
}
//FString sss = FString::Printf(TEXT("%02d_%02d_"), m, a) + FString(aiA.mName.C_Str());
FString sss = morphName;// FString::Printf(TEXT("%02d_%02d_"), m, a) + FString();
UMorphTarget *mt = NewObject<UMorphTarget>(sk, *sss);
#if WITH_EDITOR
LocalPopulateDeltas(mt, MorphDeltas, 0, sk->GetImportedModel()->LODModels[0].Sections);
#else
LocalPopulateDeltas(sk, mt, MorphDeltas, 0);
#endif
if (mt->HasValidData()) {
#if UE_VERSION_OLDER_THAN(4,20,0)
#else
{
FMorphTargetLODModel MorphLODModel;
MorphLODModel.Reset();
MorphLODModel.NumBaseMeshVerts = MorphDeltas.Num();
MorphLODModel.SectionIndices.Add(0);
MorphLODModel.Vertices = MorphDeltas;
#if UE_VERSION_OLDER_THAN(5,0,0)
if (mt->MorphLODModels.IsValidIndex(0) == false) {
mt->MorphLODModels.Add(MorphLODModel);
} else {
mt->MorphLODModels[0] = MorphLODModel;
}
#else
if (mt->GetMorphLODModels().IsValidIndex(0) == false) {
mt->GetMorphLODModels().Add(MorphLODModel);
} else {
mt->GetMorphLODModels()[0] = MorphLODModel;
}
#endif
mt->BaseSkelMesh = sk;
}
#endif
MorphTargetList.Add(mt);
}
}
}
#if WITH_EDITOR
#if UE_VERSION_OLDER_THAN(4,25,0)
#else
// to avoid no morph target
// on Immediate
VRMSetUseLegacyMeshDerivedDataKey(sk, true);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FSkeletalMeshImportData RawMesh;
sk->LoadLODImportedData(0, RawMesh);
RawMesh.MorphTargetNames = MorphNameList;
// to avoid no morph target
// on EditorRestart
sk->SaveLODImportedData(0, RawMesh);
sk->SetLODImportedDataVersions(0, ESkeletalMeshGeoImportVersions::Before_Versionning, ESkeletalMeshSkinningImportVersions::Before_Versionning);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#endif
#endif // editor
#if UE_VERSION_OLDER_THAN(5,0,0)
#else
{
FSkeletalMeshLODInfo* LODInfoPtr = sk->GetLODInfo(0);
if (LODInfoPtr) {
LODInfoPtr->ReductionSettings.NumOfTrianglesPercentage = 1.f;
}
}
#endif
{
// remove all morph & morph curve
sk->UnregisterAllMorphTarget();
#if WITH_EDITOR
#if UE_VERSION_OLDER_THAN(5,3,0)
#else
{
TArray<FName> namelist;
VRMGetSkeleton(sk)->GetCurveMetaDataNames(namelist);
for (auto name : namelist) {
FCurveMetaData* m = VRMGetSkeleton(sk)->GetCurveMetaData(name);
if (m == nullptr) continue;
if (m->Type.bMorphtarget) {
VRMGetSkeleton(sk)->RemoveCurveMetaData(name);
}
}
}
#endif
#endif
}
for (int i=0; i<MorphTargetList.Num(); ++i){
auto *mt = MorphTargetList[i];
if (i == MorphTargetList.Num() - 1) {
sk->RegisterMorphTarget(mt);
} else {
VRMGetMorphTargets(sk).Add(mt);
}
}
for (auto name : MorphNameList) {
FCurveMetaData* FoundCurveMetaData = VRMGetSkeleton(sk)->GetCurveMetaData(*name);
if (FoundCurveMetaData) {
FoundCurveMetaData->Type.bMorphtarget = true;
continue;
}
VRMGetSkeleton(sk)->AccumulateCurveMetaData(*name, false, true);
}
#if WITH_EDITOR
sk->PostEditChange();
#else
#if UE_VERSION_OLDER_THAN(4,24,0)
#else
if (sk->GetResourceForRendering()) {
if (sk->GetResourceForRendering()->LODRenderData.Num() > 0) {
#if UE_VERSION_OLDER_THAN(5,0,0)
#else
if (VRMConverter::IsImportMode() == false) {
for (auto& RenderSection : sk->GetResourceForRendering()->LODRenderData[0].RenderSections) {
RenderSection.DuplicatedVerticesBuffer.DupVertData.SetNum(1);
}
}
#endif
#if UE_VERSION_OLDER_THAN(5,4,0)
sk->GetResourceForRendering()->LODRenderData[0].InitResources(false, 0, VRMGetMorphTargets(sk), sk);
#else
{
TArray<UMorphTarget*> morphtargets;
for (auto morphtarget : VRMGetMorphTargets(sk)) {
morphtargets.Add(morphtarget);
}
#if UE_VERSION_OLDER_THAN(5,7,0)
sk->GetResourceForRendering()->LODRenderData[0].InitResources(false, 0, morphtargets, sk);
#else
sk->GetResourceForRendering()->LODRenderData[0].InitResources(false, 0, sk);
#endif
}
#endif
}
}
#endif
#endif // editor
return true;
}
VrmConvertMorphTarget::VrmConvertMorphTarget()
{
}
VrmConvertMorphTarget::~VrmConvertMorphTarget()
{
}