Add Games File

This commit is contained in:
Ivan Anikin
2026-05-19 13:42:41 +07:00
parent 7908479e0c
commit 12e7e22c1a
3672 changed files with 125922 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#pragma once
#include "CoreMinimal.h"
//#include "VRMImporterModule.h"
DECLARE_LOG_CATEGORY_EXTERN(LogVRM4ULoader, Verbose, All);
#define SPRITER_IMPORT_ERROR(FormatString, ...) \
if (!bSilent) { UE_LOG(LogVRMLoader, Warning, FormatString, __VA_ARGS__); }
#define SPRITER_IMPORT_WARNING(FormatString, ...) \
if (!bSilent) { UE_LOG(LogVRMLoader, Warning, FormatString, __VA_ARGS__); }
@@ -0,0 +1,146 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "Engine/Engine.h"
#include "Modules/ModuleManager.h"
#include "Modules/ModuleInterface.h"
#include "VRM4ULoaderLog.h"
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include "shellapi.h"
#include "Windows/HideWindowsPlatformTypes.h"
#include "Windows/WindowsApplication.h"
#include "GenericPlatform/GenericApplication.h"
#include "Framework/Application/SlateApplication.h"
#include "VrmDropFiles.h"
#include "VrmRuntimeSettings.h"
#include "Interfaces/IPluginManager.h"
class FDropMessageHandler : public IWindowsMessageHandler {
public:
virtual bool ProcessMessage(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam, int32& OutResult) override {
{
const UObject *p = UVrmDropFilesComponent::getLatestActiveComponent();
const UWorld* World = nullptr;
if (p) {
World = GEngine->GetWorldFromContextObject(p, EGetWorldErrorMode::ReturnNull);
}
{
bool bCurrentState = false;
if (World) {
switch (World->WorldType) {
case EWorldType::Game:
case EWorldType::PIE:
bCurrentState = true;
break;
}
}
static bool bDropEnable = false;
if (bDropEnable != bCurrentState) {
bDropEnable = bCurrentState;
DragAcceptFiles(hwnd, bDropEnable);
//DragAcceptFiles(GetForegroundWindow(), bDropEnable);
DragAcceptFiles(GetActiveWindow(), bDropEnable);
}
}
}
if (msg == WM_DROPFILES)
{
TCHAR NextFile[MAX_PATH] = {};
HDROP hDrop = (HDROP)wParam;
// Get the # of files being dropped.
int32 NumFiles = DragQueryFile(hDrop, -1, NULL, 0);
for (int32 File = 0; File < NumFiles; File++)
{
// Get the next filename from the HDROP info.
if (DragQueryFile(hDrop, File, NextFile, MAX_PATH) > 0)
{
FString Filepath = NextFile;
//Do whatever you want with the filepath
UVrmDropFilesComponent::StaticOnDropFilesDelegate.Broadcast(Filepath);
}
}
return false;
}
return false;
}
};
#endif
#define LOCTEXT_NAMESPACE "VRM4ULoaderModule"
DEFINE_LOG_CATEGORY(LogVRM4ULoader);
class FVRM4ULoaderModule : public IModuleInterface {
#if PLATFORM_WINDOWS
void *assimpDllHandle = nullptr;
#endif
void StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
#if PLATFORM_WINDOWS
const UVrmRuntimeSettings* Settings = GetDefault<UVrmRuntimeSettings>();
if (Settings) {
if (Settings->bDropVRMFileEnable) {
if (FSlateApplication::IsInitialized()) {
static FDropMessageHandler DropMessageHandler;// = FDropMessageHandler();
//FWindowsApplication* WindowsApplication = (FWindowsApplication*)GenericApplication.Get();
TSharedPtr<GenericApplication> App = FSlateApplication::Get().GetPlatformApplication();
if (App.IsValid()) {
auto WindowsApplication = (FWindowsApplication*)(App.Get());
//WindowsApplication->SetMessageHandler(DropMessageHandler);
WindowsApplication->AddMessageHandler(DropMessageHandler);
}
}
}
}
{
auto dllName = TEXT("assimp-vc141-mt.dll");
#if WITH_VRM4U_ASSIMP_DEBUG
dllName = TEXT("assimp-vc141-mtd.dll");
#endif
{
FString AbsPath = IPluginManager::Get().FindPlugin("VRM4U")->GetBaseDir() / TEXT("ThirdParty/assimp/bin/x64");
//FPlatformProcess::AddDllDirectory(*AbsPath);
assimpDllHandle = FPlatformProcess::GetDllHandle(*(AbsPath / dllName));
}
}
#endif
}
void ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
#if PLATFORM_WINDOWS
if (assimpDllHandle){
FPlatformProcess::FreeDllHandle(assimpDllHandle);
assimpDllHandle = nullptr;
}
#endif
}
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FVRM4ULoaderModule, VRM4ULoader)
@@ -0,0 +1,289 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmAsyncLoadAction.h"
#include "Async/TaskGraphInterfaces.h"
#include "Engine/Texture2D.h"
#include "Misc/FileHelper.h"
#include "LoaderBPFunctionLibrary.h"
#include "VrmAssetListObject.h"
#include "VRM4ULoaderLog.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/postprocess.h>
#include <assimp/material.h>
#include <assimp/GltfMaterial.h>
#include <assimp/vrm/vrmmeta.h>
#if UE_VERSION_OLDER_THAN(4,23,0)
#define TRACE_CPUPROFILER_EVENT_SCOPE(a)
#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(a)
#else
#endif
namespace {
class VrmLocalAsyncAsset {
public:
TArray<bool> NormalBoolTable;
TArray<bool> MaskBoolTable;
TArray<uint8> vrmLocalRes;
Assimp::Importer* Importer = nullptr;
const aiScene* ScenePtr = nullptr;
void Reset() {
delete Importer;
Importer = nullptr;
ScenePtr = nullptr;
NormalBoolTable.Empty();
MaskBoolTable.Empty();
vrmLocalRes.Empty();
}
};
VrmLocalAsyncAsset localAsset;
}
static bool ConvTex(UVrmAssetListObject* vrmAssetList, const aiScene* mScenePtr, const FImportOptionData* option, const int TexCount, const int SubCount) {
if (vrmAssetList == nullptr || mScenePtr == nullptr) {
return true;
}
if ((int)mScenePtr->mNumTextures <= TexCount) {
return true;
}
if (TexCount == 0 && SubCount==0) {
localAsset.NormalBoolTable.Empty();
localAsset.MaskBoolTable.Empty();
vrmAssetList->Textures.Empty();
localAsset.NormalBoolTable.SetNum(mScenePtr->mNumTextures);
localAsset.MaskBoolTable.SetNum(mScenePtr->mNumTextures);
vrmAssetList->Textures.SetNum(mScenePtr->mNumTextures);
const VRM::VRMMetadata* meta = static_cast<const VRM::VRMMetadata*>(mScenePtr->mVRMMeta);
if (meta) {
for (int i = 0; i < meta->materialNum; ++i) {
int t = 0;
t = meta->material[i].textureProperties._BumpMap;
if (localAsset.NormalBoolTable.IsValidIndex(t)) {
localAsset.NormalBoolTable[t] = true;
}
t = meta->material[i].textureProperties._OutlineWidthTexture;
if (localAsset.MaskBoolTable.IsValidIndex(t)) {
localAsset.MaskBoolTable[t] = true;
}
}
}
}
if (mScenePtr->HasTextures()) {
// Note: PNG format. Other formats are supported
//for (uint32_t i = 0; i < mScenePtr->mNumTextures; ++i) {
{
uint32_t i = TexCount;
if (SubCount == 0) {
auto& t = *mScenePtr->mTextures[i];
int Width = t.mWidth;
int Height = t.mHeight;
FString baseName = VRMConverter::NormalizeFileName(t.mFilename.C_Str());
if (baseName.Len() == 0) {
baseName = TEXT("texture") + FString::FromInt(i);
}
bool bNormalGreenFlip = localAsset.NormalBoolTable[i];
if (bNormalGreenFlip) {
baseName += TEXT("_N");
}
FString name = FString(TEXT("T_")) + baseName;
auto* pkg = GetTransientPackage();
UTexture2D* NewTexture2D = VRMLoaderUtil::CreateTextureFromImage(name, pkg, t.pcData, t.mWidth, false, localAsset.NormalBoolTable[i], bNormalGreenFlip&&(VRMConverter::IsImportMode() == false));
vrmAssetList->Textures[i] = NewTexture2D;
}
if (SubCount == 1) {
UTexture2D* NewTexture2D = vrmAssetList->Textures[i];
#if WITH_EDITOR
NewTexture2D->DeferCompression = false;
#endif
// Set options
if (localAsset.NormalBoolTable[i]) {
NewTexture2D->CompressionSettings = TC_Normalmap;
NewTexture2D->SRGB = 0;
#if WITH_EDITOR
if (VRMConverter::IsImportMode()) {
NewTexture2D->bFlipGreenChannel = true;
}
#endif
}
if (localAsset.MaskBoolTable[i]) {
// comment for material warning...
//NewTexture2D->SRGB = 0;
}
if (NewTexture2D->SRGB) {
if (option->bBC7Mode) {
NewTexture2D->CompressionSettings = TC_BC7;
}
}
if (option->bMipmapGenerateMode == false) {
#if WITH_EDITORONLY_DATA
NewTexture2D->MipGenSettings = TMGS_NoMipmaps;
#endif
}
NewTexture2D->UpdateResource();
#if WITH_EDITOR
//NewTexture2D->PostEditChange();
#endif
}
}
}
return false;
}
FVrmAsyncLoadAction::FVrmAsyncLoadAction(const FLatentActionInfo& LatentInfo, FVrmAsyncLoadActionParam &p)
: ExecutionFunction(LatentInfo.ExecutionFunction)
, OutputLink(LatentInfo.Linkage)
, CallbackTarget(LatentInfo.CallbackTarget)
, param(p)
{
}
void FVrmAsyncLoadAction::UpdateOperation(FLatentResponse& Response)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VRMLoad UpdateOperation"))
enum class ESequenceNo : uint32{
Init,
FileWait,
AssImp,
TextureLoop,
AssetCreate,
Finish,
};
static int TexCount = 0;
static int SubCount = 0;
static int FrameCount = 0;
static double StartTime = 0.f;
++FrameCount;
auto logFunc = [&](FString str="") {
UE_LOG(LogVRM4ULoader, Log, TEXT("AsyncLoad frame=%04d(%02.2lf), SequenceNo=%02d %s"), FrameCount, FPlatformTime::Seconds()-StartTime, SequenceCount, *str);
};
auto logTexFunc = [&](int TexCount) {
UE_LOG(LogVRM4ULoader, Log, TEXT("AsyncLoad frame=%04d(%02.2lf), SequenceNo=%02d TextureCount=%02d"), FrameCount, FPlatformTime::Seconds() - StartTime, SequenceCount, TexCount);
};
// async file load
if (SequenceCount == (int)ESequenceNo::Init) {
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VRMLoad UpdateOperation init"))
TexCount = 0;
SubCount = 0;
FrameCount = 0;
StartTime = FPlatformTime::Seconds();
logFunc("Begin");
++SequenceCount;
TFunction< void() > f = [&] {
if (FFileHelper::LoadFileToArray(localAsset.vrmLocalRes, *param.filepath)) {
param.pData = localAsset.vrmLocalRes.GetData();
param.dataSize = localAsset.vrmLocalRes.Num();
}
};
auto p2 = ENamedThreads::AnyBackgroundThreadNormalTask;
//p2 = ENamedThreads::AnyBackgroundThreadNormalTask;
//p2 = ENamedThreads::GameThread_Local;
t2 = FFunctionGraphTask::CreateAndDispatchWhenReady(f, TStatId(), nullptr, p2);
return;
}
if (SequenceCount == (int)ESequenceNo::FileWait) {
if (t2->IsComplete()) {
logFunc();
++SequenceCount;
param.OutVrmAsset = Cast<UVrmAssetListObject>(StaticDuplicateObject(param.InVrmAsset, GetTransientPackage(), NAME_None));
}
return;
}
if (SequenceCount == (int)ESequenceNo::AssImp) {
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VRMLoad UpdateOperation assimp"))
logFunc();
++SequenceCount;
localAsset.Importer = new Assimp::Importer();
localAsset.ScenePtr = localAsset.Importer->ReadFileFromMemory(localAsset.vrmLocalRes.GetData(), localAsset.vrmLocalRes.Num(),
aiProcess_Triangulate | aiProcess_MakeLeftHanded | aiProcess_CalcTangentSpace | aiProcess_GenSmoothNormals | aiProcess_OptimizeMeshes,
"vrm");
return;
}
if (SequenceCount == (int)ESequenceNo::TextureLoop) {
TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("VRMLoad UpdateOperation texture %d"), SubCount))
if (localAsset.ScenePtr == nullptr) {
Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);
localAsset.Reset();
return;
}
if (TexCount < (int)localAsset.ScenePtr->mNumTextures) {
if (SubCount == 0) {
ConvTex(param.OutVrmAsset, localAsset.ScenePtr, &param.OptionForRuntimeLoad, TexCount, 0);
}
if (SubCount == 2) {
ConvTex(param.OutVrmAsset, localAsset.ScenePtr, &param.OptionForRuntimeLoad, TexCount, 1);
}
++SubCount;
if (SubCount >= 4) {
logTexFunc(TexCount);
++TexCount;
SubCount = 0;
}
} else {
logFunc();
++SequenceCount;
}
return;
}
if (SequenceCount == (int)ESequenceNo::AssetCreate) {
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VRMLoad UpdateOperation asset"))
logFunc();
++SequenceCount;
if (param.pData) {
ULoaderBPFunctionLibrary::LoadVRMFileFromMemory(param.InVrmAsset, param.OutVrmAsset, param.filepath, param.pData, param.dataSize);
} else {
ULoaderBPFunctionLibrary::LoadVRMFile(param.InVrmAsset, param.OutVrmAsset, param.filepath, param.OptionForRuntimeLoad);
}
return;
}
if (SequenceCount == (int)ESequenceNo::Finish) {
logFunc("End");
Response.FinishAndTriggerIf(true, ExecutionFunction, OutputLink, CallbackTarget);
localAsset.Reset();
}
}
@@ -0,0 +1,55 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#pragma once
#include "CoreMinimal.h"
#include "UObject/WeakObjectPtr.h"
#include "Engine/LatentActionManager.h"
#include "LatentActions.h"
#include "VrmUtil.h"
class UVrmAssetListObject;
struct FImportOptionData;
class FVrmAsyncLoadActionParam {
public:
const UVrmAssetListObject* InVrmAsset;
UVrmAssetListObject*& OutVrmAsset;
const FImportOptionData OptionForRuntimeLoad;
const FString filepath;
const uint8* pData;
size_t dataSize;
};
class FVrmAsyncLoadAction : public FPendingLatentAction
{
public:
FName ExecutionFunction;
int32 OutputLink;
FWeakObjectPtr CallbackTarget;
int SequenceCount = 0;
FGraphEventRef t2 = nullptr;
FVrmAsyncLoadActionParam param;
FVrmAsyncLoadAction(const FLatentActionInfo& LatentInfo, FVrmAsyncLoadActionParam &);
virtual void UpdateOperation(FLatentResponse& Response) override;
#if WITH_EDITOR
// Returns a human readable description of the latent operation's current state
virtual FString GetDescription() const override
{
//static const FNumberFormattingOptions DelayTimeFormatOptions = FNumberFormattingOptions()
// .SetMinimumFractionalDigits(3)
// .SetMaximumFractionalDigits(3);
//return FText::Format(NSLOCTEXT("DelayAction", "DelayActionTimeFmt", "Delay ({0} seconds left)"), FText::AsNumber(TimeRemaining, &DelayTimeFormatOptions)).ToString();
return FString("");
}
#endif
};
@@ -0,0 +1,926 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvert.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>
#include "UObject/Package.h"
//static void assimpDummy() {
//volatile float f = Assimp::Math::PI<float>();
//}
static bool bImportMode = false;
bool VRMConverter::IsImportMode() {
return bImportMode;
}
void VRMConverter::SetImportMode(bool b) {
bImportMode = b;
}
UPackage *VRMConverter::CreatePackageFromImportMode(UPackage *p, const FString &name) {
FString n = p->GetName();
n += TEXT("/") + name;
#if UE_VERSION_OLDER_THAN(4,26,0)
return CreatePackage(nullptr, *n);
#else
return CreatePackage(*n);
#endif
}
FString VRMConverter::NormalizeFileName(const char *str) {
FString ret = UTF8_TO_TCHAR(str);
return NormalizeFileName(ret);
}
FString VRMConverter::NormalizeFileName(const FString &str) {
FString ret = str;
FText error;
if (!FName::IsValidXName(*ret, INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, &error)) {
FString s = INVALID_OBJECTNAME_CHARACTERS;
s += INVALID_LONGPACKAGE_CHARACTERS;
auto a = s.GetCharArray();
for (int i = 0; i < a.Num(); ++i) {
FString tmp;
tmp.AppendChar(a[i]);
ret = ret.Replace(tmp.GetCharArray().GetData(), TEXT("_"));
//int ind;
//if (ret.FindChar(s[i], ind)) {
//}
}
}
return ret;
}
static bool hasInvalidBoneName(const aiString &s) {
for (uint32_t i = 0; i < s.length; ++i) {
if (s.data[i] >= 0 && s.data[i] <= 0x7e) {
continue;
}
return true;
}
return false;
}
static int totalCount = 0;
static bool AddReplaceList(const aiNode *node, TMap<FString, FString> &map) {
if (node == nullptr) {
return true;
}
if (hasInvalidBoneName(node->mName)) {
FString s = TEXT("replace_") + FString::FromInt(totalCount);
if (map.Find(UTF8_TO_TCHAR(node->mName.C_Str())) == nullptr) {
map.Add(UTF8_TO_TCHAR(node->mName.C_Str()), s);
totalCount++;
}
}
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
AddReplaceList(node->mChildren[i], map);
}
return true;
}
static bool ReplaceNodeName(aiNode *node, const TMap<FString, FString> &map) {
if (node == nullptr) {
return true;
}
auto a = map.Find(UTF8_TO_TCHAR(node->mName.C_Str()));
if (a) {
node->mName.Set(TCHAR_TO_ANSI(**a));
}
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
ReplaceNodeName(node->mChildren[i], map);
}
return true;
}
static bool LocalMakeName(aiNode* node) {
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
LocalMakeName(node->mChildren[i]);
}
auto s = VRMUtil::MakeName(UTF8_TO_TCHAR(node->mName.C_Str()), true);
node->mName = TCHAR_TO_UTF8(*s);
return true;
}
static bool LocalNormalizeBone(aiNode *node) {
for (uint32_t i = 0; i < node->mNumChildren; ++i) {
LocalNormalizeBone(node->mChildren[i]);
}
auto s = VRMUtil::GetSafeNewName(UTF8_TO_TCHAR(node->mName.C_Str()));
node->mName = TCHAR_TO_UTF8(*s);
return true;
}
bool VRMConverter::NormalizeBoneName(const aiScene *mScenePtr) {
if (mScenePtr == nullptr) {
return false;
}
if (VRMConverter::Options::Get().IsForceOriginalBoneName()) {
}else{
LocalMakeName(mScenePtr->mRootNode);
// aiBone name
for (uint32 m = 0; m < mScenePtr->mNumMeshes; ++m) {
auto* aiM = mScenePtr->mMeshes[m];
for (uint32 b = 0; b < aiM->mNumBones; ++b) {
auto* aiB = aiM->mBones[b];
auto s = VRMUtil::MakeName(UTF8_TO_TCHAR(aiB->mName.C_Str()), true);
aiB->mName = TCHAR_TO_UTF8(*s);
}
}
// spring, collision name
VRM::VRMMetadata* meta = reinterpret_cast<VRM::VRMMetadata*>(mScenePtr->mVRMMeta);
if (meta) {
for (int s = 0; s < meta->springNum; ++s) {
for (int b = 0; b < meta->springs[s].boneNum; ++b) {
auto str = VRMUtil::MakeName(UTF8_TO_TCHAR(meta->springs[s].bones_name[b].C_Str()), true);
meta->springs[s].bones_name[b] = TCHAR_TO_UTF8(*str);
}
}
for (int c = 0; c < meta->colliderGroupNum; ++c) {
auto str = VRMUtil::MakeName(UTF8_TO_TCHAR(meta->colliderGroups[c].node_name.C_Str()), true);
meta->colliderGroups[c].node_name = TCHAR_TO_UTF8(*str);
}
}
}
return true;
/*
//auto p = const_cast<aiScene*>(mScenePtr);
//if (p == nullptr) return false;
TMap<FString, FString> replaceTable;
totalCount = 0;
//mScenePtr->mMeshes[0]->bon
AddReplaceList(mScenePtr->mRootNode, replaceTable);
for (uint32_t m = 0; m < mScenePtr->mNumMeshes; ++m) {
if (hasInvalidBoneName(mScenePtr->mMeshes[m]->mName)) {
FString s = TEXT("replace_") + FString::FromInt(totalCount);
if (replaceTable.Find(UTF8_TO_TCHAR(mScenePtr->mMeshes[m]->mName.C_Str())) == nullptr) {
replaceTable.Add(UTF8_TO_TCHAR(mScenePtr->mMeshes[m]->mName.C_Str()), s);
totalCount++;
}
}
}
// replace!
ReplaceNodeName(mScenePtr->mRootNode, replaceTable);
for (uint32_t m = 0; m < mScenePtr->mNumMeshes; ++m) {
if (auto a = replaceTable.Find(UTF8_TO_TCHAR(mScenePtr->mMeshes[m]->mName.C_Str()))){
mScenePtr->mMeshes[m]->mName.Set(TCHAR_TO_ANSI(**a));
}
auto &aiM = mScenePtr->mMeshes[m];
for (uint32_t b = 0; b < aiM->mNumBones; ++b) {
if (auto a = replaceTable.Find(UTF8_TO_TCHAR(aiM->mBones[b]->mName.C_Str()))) {
aiM->mBones[b]->mName.Set(TCHAR_TO_ANSI(**a));
}
}
}
{
VRM::VRMMetadata *meta = reinterpret_cast<VRM::VRMMetadata*>(mScenePtr->mVRMMeta);
for (int s = 0; s < meta->springNum; ++s) {
auto &a = meta->springs[s];
if (a.bones_name == nullptr) {
continue;
}
for (int b = 0; b < a.boneNum; ++b) {
if (auto str = replaceTable.Find(UTF8_TO_TCHAR(a.bones_name[b].C_Str()))) {
a.bones_name[b].Set(TCHAR_TO_ANSI(**str));
}
}
}
for (int c = 0; c < meta->colliderGroupNum; ++c) {
auto &a = meta->colliderGroups[c];
if (auto str = replaceTable.Find(UTF8_TO_TCHAR(a.node_name.C_Str()))) {
a.node_name.Set(TCHAR_TO_ANSI(**str));
}
}
for (auto &a : meta->humanoidBone) {
if (auto str = replaceTable.Find(UTF8_TO_TCHAR(a.nodeName.C_Str()))) {
a.nodeName.Set(TCHAR_TO_ANSI(**str));
}
}
}
return true;
*/
}
////
VRMConverter::Options& VRMConverter::Options::Get(){
static VRMConverter::Options o;
return o;
}
USkeleton *VRMConverter::Options::GetSkeleton() {
if (ImportOption == nullptr) return nullptr;
return ImportOption->Skeleton;
}
bool VRMConverter::Options::IsSkipNoMeshBone() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bSkipNoMeshBone;
#else
return false;
#endif
}
bool VRMConverter::Options::IsSkipMorphTarget() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bSkipMorphTarget || IsDebugNoMesh();
#else
return false;
#endif
}
bool VRMConverter::Options::IsEnableMorphTargetNormal() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bEnableMorphTargetNormal;
#else
return false;
#endif
}
bool VRMConverter::Options::IsForceOriginalMorphTargetName() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bForceOriginalMorphTargetName;
#else
return false;
#endif
}
bool VRMConverter::Options::IsRemoveBlendShapeGroupPrefix() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bRemoveBlendShapeGroupPrefix;
#else
return false;
#endif
}
bool VRMConverter::Options::IsForceOriginalBoneName() const {
if (ImportOption == nullptr) return false;
return ImportOption->bForceOriginalBoneName;
}
bool VRMConverter::Options::IsGenerateHumanoidRenamedMesh() const {
if (ImportOption == nullptr) return false;
return ImportOption->bGenerateHumanoidRenamedMesh;
}
bool VRMConverter::Options::IsGenerateIKBone() const {
if (ImportOption == nullptr) return false;
return ImportOption->bGenerateIKBone;
}
bool VRMConverter::Options::IsGenerateRigIK() const {
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bGenerateRigIK;
#else
return false;
#endif
}
bool VRMConverter::Options::IsMobileBone() const {
bool ret = false;
#if (PLATFORM_WINDOWS)
#else
ret = true;
#endif
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bMobileBone;
#else
return ret;
#endif
}
int VRMConverter::Options::GetBoneWeightInfluenceNum() const {
int ret = 8;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return FMath::Clamp(ImportOption->BoneWeightInfluenceNum, 1, 8);
#else
return ret;
#endif
}
bool VRMConverter::Options::IsDebugIgnoreVRMValidation() const {
bool ret = false;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bIgnoreVRMValidation;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsDebugOneBone() const {
bool ret = false;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bDebugOneBone;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsDebugNoMesh() const {
bool ret = false;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bDebugNoMesh;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsDebugNoMaterial() const {
bool ret = false;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bDebugNoMaterial;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsSimpleRootBone() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bSimpleRoot;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsActiveBone() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bActiveBone;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsSkipPhysics() const {
bool ret = true;
if (IsDebugOneBone()) {
return true;
}
if (ImportOption == nullptr) return ret;
if (ImportOption->bUEFN) return true;
return ImportOption->bSkipPhysics;
}
bool VRMConverter::Options::IsSkipRetargeter() const {
bool ret = false;
if (ImportOption == nullptr) return ret;
return ImportOption->bSkipRetargeter;
}
bool VRMConverter::Options::IsForceOpaque() const {
if (ImportOption == nullptr) return false;
return ImportOption->bForceOpaque;
}
bool VRMConverter::Options::IsForceTwoSided() const {
if (ImportOption == nullptr) return false;
return ImportOption->bForceTwoSided;
}
bool VRMConverter::Options::IsSingleUAssetFile() const {
if (ImportOption == nullptr) return false;
if (ImportOption->bUEFN) return false;
return ImportOption->bSingleUAssetFile;
}
bool VRMConverter::Options::IsDefaultGridTextureMode() const {
bool ret = false;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bDefaultGridTextureMode;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsBC7Mode() const {
if (ImportOption == nullptr) return true;
return ImportOption->bBC7Mode;
}
bool VRMConverter::Options::IsMipmapGenerateMode() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bMipmapGenerateMode;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsGenerateOutlineMaterial() const {
bool ret = true;
if (ImportOption == nullptr) return true;
return ImportOption->bGenerateOutlineMaterial;
}
bool VRMConverter::Options::IsMergeMaterial() const {
bool ret = true;
if (ImportOption == nullptr) return true;
return ImportOption->bMergeMaterial;
}
bool VRMConverter::Options::IsMergePrimitive() const {
if (ImportOption == nullptr) return true;
return ImportOption->bMergePrimitive;
}
bool VRMConverter::Options::IsOptimizeVertex() const {
return false;
//if (ImportOption == nullptr) return true;
//return ImportOption->bOptimizeVertex;
}
bool VRMConverter::Options::IsRemoveDegenerateTriangles() const {
if (ImportOption == nullptr) return true;
return ImportOption->bRemoveDegenerateTriangles;
}
bool VRMConverter::Options::IsUE5Material() const {
if (ImportOption == nullptr) return false;
return ImportOption->bUseUE5Material;
}
static bool bbVRM0 = false;
static bool bbVRM10 = false;
void VRMConverter::Options::SetVRM0Model(bool bVRM) {
bbVRM0 = bVRM;
bbVRM10 = !bVRM;
}
void VRMConverter::Options::SetVRM10Model(bool bVRM) {
bbVRM10 = bVRM;
bbVRM0 = !bVRM;
}
bool VRMConverter::Options::IsVRM0Model() const {
return bbVRM0;
}
bool VRMConverter::Options::IsVRM10Model() const {
return bbVRM10;
}
bool VRMConverter::Options::IsRemoveRootBoneRotation() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bRemoveRootBoneRotation;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsRemoveRootBonePosition() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bRemoveRootBonePosition;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsVRM10RemoveLocalRotation() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bVrm10RemoveLocalRotation;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsVRM10BindToRestPose() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bVrm10UseBindToRestPose;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsVRM10Bindpose() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bVrm10Bindpose;
#else
return ret;
#endif
}
bool VRMConverter::Options::IsVRMModel() const {
return IsVRM0Model() || IsVRM10Model();
}
static bool bbVRMA = false;
void VRMConverter::Options::SetVRMAModel(bool bVRMA) {
bbVRMA = bVRMA;
}
bool VRMConverter::Options::IsVRMAModel() const {
return bbVRMA;
}
static bool bbBVH = false;
void VRMConverter::Options::SetBVHModel(bool bBVH) {
bbBVH = bBVH;
}
bool VRMConverter::Options::IsBVHModel() const {
return bbBVH;
}
static bool bbPMX = false;
void VRMConverter::Options::SetPMXModel(bool bVRM) {
bbPMX = bVRM;
}
bool VRMConverter::Options::IsPMXModel() const {
return bbPMX;
}
static bool bbNoMesh = false;
void VRMConverter::Options::SetNoMesh(bool bNoMesh) {
bbNoMesh = bNoMesh;
}
bool VRMConverter::Options::IsNoMesh() const {
return bbNoMesh;
}
void VRMConverter::Options::ClearModelType() {
bbVRM0 = false;
bbVRM10 = false;
bbVRMA = false;
bbBVH = false;
bbPMX = false;
bbNoMesh = false;
}
bool VRMConverter::Options::IsForceOverride() const {
bool ret = true;
#if WITH_EDITOR
if (ImportOption == nullptr) return ret;
return ImportOption->bForceOverride;
#else
return ret;
#endif
}
float VRMConverter::Options::GetModelScale() const {
if (ImportOption == nullptr) return 1.f;
return ImportOption->ModelScale;
}
float VRMConverter::Options::GetAnimationTranslateScale() const {
if (ImportOption == nullptr) return 1.f;
return ImportOption->AnimationTranslateScale;
}
float VRMConverter::Options::GetAnimationPlayRateScale() const {
if (ImportOption == nullptr) return 1.f;
return ImportOption->PlayRateScale;
}
bool VRMConverter::Options::IsAPoseRetarget() const {
if (IsDebugOneBone()) {
return false;
}
#if WITH_EDITOR
if (ImportOption == nullptr) return false;
return ImportOption->bAPoseRetarget;
#else
return false;
#endif
}
static EVRMImportMaterialType mType = EVRMImportMaterialType::VRMIMT_Auto;
void VRMConverter::Options::SetMaterialType(EVRMImportMaterialType t) {
mType = t;
}
EVRMImportMaterialType VRMConverter::Options::GetMaterialType() const {
if (ImportOption == nullptr) return mType;
return ImportOption->MaterialType;
}
////
VrmConvert::VrmConvert()
{
}
VrmConvert::~VrmConvert()
{
}
FString VRM4U_GetPackagePath(UPackage* Outer) {
if (Outer == nullptr) return "";
if (Outer != GetTransientPackage()) {
FString s = Outer->GetPathName();
FString s1, s2;
s.Split(TEXT("/"), &s1, &s2, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
return s1 + TEXT("/");
}
return "";
}
UPackage* VRM4U_CreatePackage(UPackage* Outer, FName Name) {
if (Outer == nullptr) return nullptr;
UPackage* pkg = Outer;
if (Outer != GetTransientPackage()) {
FString s = Outer->GetPathName();
FString s1, s2;
s.Split(TEXT("/"), &s1, &s2, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
#if UE_VERSION_OLDER_THAN(4,26,0)
pkg = CreatePackage(nullptr, *(s1 + TEXT("/") + Name.ToString()));
#else
pkg = CreatePackage(*(s1 + TEXT("/") + Name.ToString()));
#endif
}
return pkg;
}
UObject* VRM4U_StaticDuplicateObject(UObject const* SourceObject, UObject* DestOuter, const FName DestName, EObjectFlags FlagMask, UClass* DestClass, EDuplicateMode::Type DuplicateMode, EInternalObjectFlags InternalFlagsMask) {
if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) {
DestOuter = VRM4U_CreatePackage(Cast<UPackage>(DestOuter), DestName);
}
return StaticDuplicateObject(SourceObject,
DestOuter, DestName,
FlagMask, DestClass, DuplicateMode, InternalFlagsMask);
}
///
template<typename T1>
static void copyVector(VRM::vec4 &v, const T1& t) {
v[3] = 1.f;
if (t.Size() > 0) v[0] = t[0].GetFloat();
if (t.Size() > 1) v[1] = t[1].GetFloat();
if (t.Size() > 2) v[2] = t[2].GetFloat();
if (t.Size() > 3) v[3] = t[3].GetFloat();
}
int VRMConverter::GetThumbnailTextureIndex() const {
if (VRMConverter::Options::Get().IsVRM0Model()) {
return -1;
}
auto& meta = jsonData.doc["extensions"]["VRMC_vrm"]["meta"];
for (auto m = meta.MemberBegin(); m != meta.MemberEnd(); ++m) {
FString key = UTF8_TO_TCHAR((*m).name.GetString());
if (key == "thumbnailImage") {
return (*m).value.GetInt();
}
}
return -1;
}
bool VRMConverter::GetMatParam(VRM::VRMMaterial &m, int matNo) const {
if (VRMConverter::Options::Get().IsVRM0Model()) {
const VRM::VRMMetadata* meta = static_cast<const VRM::VRMMetadata*>(aiData->mVRMMeta);
if (meta == nullptr) return false;
if (matNo >= meta->materialNum) {
return false;
}
m.CopyFrom(meta->material[matNo]);
return true;
}
memset(&m.floatProperties, 0, sizeof(m.floatProperties));
memset(&m.textureProperties, 0, sizeof(m.textureProperties));
memset(&m.vectorProperties, 0, sizeof(m.vectorProperties));
m.vectorProperties._Color[0] = 1.f;
m.vectorProperties._Color[1] = 1.f;
m.vectorProperties._Color[2] = 1.f;
m.vectorProperties._Color[3] = 1.f;
m.vectorProperties._ShadeColor[0] = 1.f;
m.vectorProperties._ShadeColor[1] = 1.f;
m.vectorProperties._ShadeColor[2] = 1.f;
m.vectorProperties._ShadeColor[3] = 1.f;
m.vectorProperties._OutlineColor[0] = 0.f;
m.vectorProperties._OutlineColor[1] = 0.f;
m.vectorProperties._OutlineColor[2] = 0.f;
m.vectorProperties._OutlineColor[3] = 1.f;
m.floatProperties._BumpScale = 1.f;
m.floatProperties._ReceiveShadowRate = 1.f;
m.floatProperties._OutlineLightingMix = 1.f;
m.floatProperties._OutlineWidth = 0.1f;
m.floatProperties._OutlineWidthMode = 1.f;
m.textureProperties._MainTex = -1;
m.textureProperties._ShadeTexture = -1;
m.textureProperties._BumpMap = -1;
m.textureProperties._ReceiveShadowTexture = -1;
m.textureProperties._ShadingGradeTexture = -1;
m.textureProperties._RimTexture = -1;
m.textureProperties._SphereAdd = -1;
m.textureProperties._EmissionMap = -1;
m.textureProperties._OutlineWidthTexture = -1;
m.textureProperties._UvAnimMaskTexture = -1;
if (jsonData.doc.HasMember("materials") == false) {
return true;
}
if (jsonData.doc["materials"].IsArray() == false) {
return true;
}
auto& mat = jsonData.doc["materials"].GetArray()[matNo];
m.name = mat["name"].GetString();
// texture
{
if (mat["extensions"]["VRMC_materials_mtoon"]["matcapTexture"]["index"].IsInt()) {
m.textureProperties._SphereAdd = mat["extensions"]["VRMC_materials_mtoon"]["matcapTexture"]["index"].GetInt();
}
if (mat["extensions"]["VRMC_materials_mtoon"]["rimMultiplyTexture"]["index"].IsInt()) {
m.textureProperties._RimTexture = mat["extensions"]["VRMC_materials_mtoon"]["rimMultiplyTexture"]["index"].GetInt();
}
}
// float
{
//m.floatProperties._Cutoff;
m.floatProperties._BumpScale = 1.f;
m.floatProperties._ReceiveShadowRate = 1.f;
m.floatProperties._ShadeShift = mat["extensions"]["VRMC_materials_mtoon"]["shadingShiftFactor"].GetFloat();
m.floatProperties._ShadeToony = mat["extensions"]["VRMC_materials_mtoon"]["shadingToonyFactor"].GetFloat();
//m.floatProperties._LightColorAttenuation;
//m.floatProperties._IndirectLightIntensity;
m.floatProperties._RimLightingMix = mat["extensions"]["VRMC_materials_mtoon"]["rimLightingMixFactor"].GetFloat();
m.floatProperties._RimFresnelPower = mat["extensions"]["VRMC_materials_mtoon"]["parametricRimFresnelPowerFactor"].GetFloat();
m.floatProperties._RimLift = mat["extensions"]["VRMC_materials_mtoon"]["parametricRimLiftFactor"].GetFloat();
m.floatProperties._OutlineWidth = mat["extensions"]["VRMC_materials_mtoon"]["outlineWidthFactor"].GetFloat() * 100.f;
//m.floatProperties._OutlineScaledMaxDistance;
m.floatProperties._OutlineLightingMix = mat["extensions"]["VRMC_materials_mtoon"]["outlineLightingMixFactor"].GetFloat();
m.floatProperties._UvAnimScrollX = mat["extensions"]["VRMC_materials_mtoon"]["uvAnimationScrollXSpeedFactor"].GetFloat();
m.floatProperties._UvAnimScrollY = mat["extensions"]["VRMC_materials_mtoon"]["uvAnimationScrollYSpeedFactor"].GetFloat();
m.floatProperties._UvAnimRotation = mat["extensions"]["VRMC_materials_mtoon"]["uvAnimationRotationSpeedFactor"].GetFloat();
//m.floatProperties._MToonVersion;
//m.floatProperties._DebugMode;
//m.floatProperties._BlendMode;
m.floatProperties._OutlineWidthMode = 1.f;
{
FString s = mat["extensions"]["VRMC_materials_mtoon"]["outlineWidthMode"].GetString();
if (s == "none") {
m.floatProperties._OutlineWidthMode = 0.f;
m.floatProperties._OutlineWidth = 0;
}
}
//m.floatProperties._OutlineColorMode;
//m.floatProperties._CullMode;
//m.floatProperties._OutlineCullMode;
//m.floatProperties._SrcBlend;
//m.floatProperties._DstBlend;
m.floatProperties._ZWrite = mat["extensions"]["VRMC_materials_mtoon"]["transparentWithZWrite"].GetBool() ? 1.f : 0.f;
}
// vector
{
{
auto t = mat["pbrMetallicRoughness"]["baseColorFactor"].GetArray();
copyVector(m.vectorProperties._Color, t);
}
{
auto t = mat["extensions"]["VRMC_materials_mtoon"]["shadeColorFactor"].GetArray();
if (t.Size()) {
copyVector(m.vectorProperties._ShadeColor, t);
m.vectorProperties._ShadeColor[3] = m.vectorProperties._Color[3];
}
}
{
auto t = mat["extensions"]["VRMC_materials_mtoon"]["parametricRimColorFactor"].GetArray();
if (t.Size()) {
copyVector(m.vectorProperties._RimColor, t);
}
}
{
auto t = mat["emissiveFactor"].GetArray();
if (t.Size()) {
copyVector(m.vectorProperties._EmissionColor, t);
}
}
{
auto t = mat["extensions"]["VRMC_materials_mtoon"]["outlineColorFactor"].GetArray();
if (t.Size()) {
copyVector(m.vectorProperties._OutlineColor, t);
}
}
{
auto t = mat["extensions"]["VRMC_materials_mtoon"]["matcapFactor"].GetArray();
if (t.Size()) {
//copyVector(m.vectorProperties._OutlineColor, t);
}
}
}
return true;
}
@@ -0,0 +1,383 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvertHumanoid.h"
#include "VrmConvert.h"
#include "VrmUtil.h"
#include "VrmAssetListObject.h"
#include "VrmMetaObject.h"
#include "Engine/SkeletalMesh.h"
#include "RenderingThread.h"
#include "Rendering/SkeletalMeshModel.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "Rendering/SkeletalMeshLODRenderData.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "Animation/MorphTarget.h"
#include "Animation/Skeleton.h"
#include "Animation/NodeMappingContainer.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/postprocess.h>
#include <assimp/GltfMaterial.h>
#include <assimp/vrm/vrmmeta.h>
static void renameToHumanoidBone(USkeletalMesh *targetSK, const UVrmMetaObject *meta, const USkeletalMesh *srcSK) {
USkeleton *targetSkeleton = VRMGetSkeleton(targetSK);
UNodeMappingContainer* rig = nullptr;
if (VRMGetNodeMappingData(targetSK).Num()) {
rig = VRMGetNodeMappingData(targetSK)[0];
}
UNodeMappingContainer* src_rig = nullptr;
if (VRMGetNodeMappingData(srcSK).Num()) {
src_rig = VRMGetNodeMappingData(srcSK)[0];
}
if (meta == nullptr) {
return;
}
//k->RemoveBonesFromSkeleton()
auto &allbone = const_cast<TArray<FMeshBoneInfo> &>(targetSkeleton->GetReferenceSkeleton().GetRawRefBoneInfo());
TMap<FName, FName> changeTable;
{
bool bHasRoot = false;
for (auto& a : allbone) {
if (a.Name == TEXT("Root")) {
a.Name = TEXT("root");
#if WITH_EDITORONLY_DATA
a.ExportName = TEXT("root");
#endif
bHasRoot = true;
break;
}
}
if (bHasRoot == false) {
if (allbone[0].Name != TEXT("hip")) {
allbone[0].Name = TEXT("root");
#if WITH_EDITORONLY_DATA
allbone[0].ExportName = TEXT("root");
#endif
}
}
}
for (auto &a : allbone) {
auto p = meta->humanoidBoneTable.FindKey(a.Name.ToString());
if (p == nullptr) {
continue;
}
if (p->IsEmpty()) {
continue;
}
for (auto &b : allbone) {
if (a == b) continue;
if (b.Name == **p) {
b.Name = *(b.Name.ToString() + TEXT("_renamed_vrm4u"));
}
}
changeTable.Add(a.Name, **p);
a.Name = **p;
#if WITH_EDITORONLY_DATA
a.ExportName = **p;
#endif
}
const_cast<FReferenceSkeleton&>(targetSkeleton->GetReferenceSkeleton()).RebuildRefSkeleton(targetSkeleton, true);
VRMSetRefSkeleton(targetSK, targetSkeleton->GetReferenceSkeleton());
#if WITH_EDITOR
#if UE_VERSION_OLDER_THAN(4,20,0)
#elif UE_VERSION_OLDER_THAN(5,4,0)
if (src_rig) {
rig->SetSourceAsset(src_rig->GetSourceAsset());
rig->SetTargetAsset(targetSK);
targetSkeleton->RefreshRigConfig();
{
auto &table = src_rig->GetNodeMappingTable();
for (auto &t : table) {
FName n = t.Value;
{
auto *ret = changeTable.Find(t.Value);
if (ret) {
n = *ret;
}
}
rig->AddMapping(t.Key, n);
targetSkeleton->SetRigBoneMapping(t.Key, n);
}
}
rig->PostEditChange();
}
#else
// ue5.4 no rig data
#endif
#endif
}
static void renameToUE4Bone(USkeletalMesh *targetSK, UVrmMetaObject *meta, const USkeletalMesh *srcSK) {
USkeleton *targetSkeleton = VRMGetSkeleton(targetSK);
UNodeMappingContainer* rig = nullptr;
if (VRMGetNodeMappingData(targetSK).Num()) {
rig = VRMGetNodeMappingData(targetSK)[0];
}
UNodeMappingContainer* src_rig = nullptr;
if (VRMGetNodeMappingData(srcSK).Num()) {
src_rig = VRMGetNodeMappingData(srcSK)[0];
}
//k->RemoveBonesFromSkeleton()
auto &allbone = const_cast<TArray<FMeshBoneInfo> &>(targetSkeleton->GetReferenceSkeleton().GetRawRefBoneInfo());
TMap<FName, FName> changeTable;
allbone[0].Name = TEXT("root");
#if WITH_EDITORONLY_DATA
allbone[0].ExportName = TEXT("root");
#endif
for (auto &a : allbone) {
FString toVRMBone;
for (auto& tmp : meta->humanoidBoneTable) {
if (a.Name.ToString().ToLower() != tmp.Value.ToLower()) continue;
toVRMBone = tmp.Key;
break;
}
if (toVRMBone == "") continue;
FString toUE4Bone;
for (auto& tmp : VRMUtil::table_ue4_vrm) {
if (toVRMBone.ToLower() != tmp.BoneVRM.ToLower()) continue;
toUE4Bone = tmp.BoneUE4;
break;
}
if (toUE4Bone == "") continue;
FString newName = toUE4Bone;
for (auto &b : allbone) {
if (a == b) continue;
if (b.Name == *(newName.ToLower())) {
b.Name = *(b.Name.ToString() + TEXT("_renamed_vrm4u"));
}
}
changeTable.Add(a.Name, *newName);
a.Name = *newName;
#if WITH_EDITORONLY_DATA
a.ExportName = *newName;
#endif
}
const_cast<FReferenceSkeleton&>(targetSkeleton->GetReferenceSkeleton()).RebuildRefSkeleton(targetSkeleton, true);
VRMSetRefSkeleton(targetSK, targetSkeleton->GetReferenceSkeleton());
#if WITH_EDITOR
#if UE_VERSION_OLDER_THAN(4,20,0)
#elif UE_VERSION_OLDER_THAN(5,4,0)
if (src_rig) {
rig->SetSourceAsset(src_rig->GetSourceAsset());
rig->SetTargetAsset(targetSK);
targetSkeleton->RefreshRigConfig();
{
auto &table = src_rig->GetNodeMappingTable();
for (auto &t : table) {
FName n = t.Value;
{
auto *ret = changeTable.Find(t.Value);
if (ret) {
n = *ret;
}
}
rig->AddMapping(t.Key, n);
targetSkeleton->SetRigBoneMapping(t.Key, n);
}
}
rig->PostEditChange();
}
#else
// ue5.4 no rig data
#endif
#endif
}
bool VRMConverter::ConvertHumanoid(UVrmAssetListObject *vrmAssetList) {
if (Options::Get().IsGenerateHumanoidRenamedMesh() == false) {
return true;
}
const USkeletalMesh *src_sk = vrmAssetList->SkeletalMesh;
const USkeleton *src_k = VRMGetSkeleton(src_sk);
const UNodeMappingContainer *src_rig = nullptr;
if (VRMGetNodeMappingData(src_sk).Num()) {
src_rig = VRMGetNodeMappingData(src_sk)[0];
}
USkeletalMesh *new_sk[2] = {};
USkeleton *new_k[2] = {};
UNodeMappingContainer *new_rig[2] = {};
// 0: humanoid
// 1: ue4mannequin
for (int i = 0; i < 2; ++i) {
FString name_skeleton;
FString name_mesh;
FString name_rig;
if (i == 0) {
name_skeleton = (TEXT("SKEL_") + vrmAssetList->BaseFileName + TEXT("_humanoid"));
name_mesh = (FString(TEXT("SK_")) + vrmAssetList->BaseFileName + TEXT("_humanoid"));
name_rig = (FString(TEXT("RIG_")) + vrmAssetList->BaseFileName + TEXT("_humanoid"));
} else {
name_skeleton = (TEXT("SKEL_") + vrmAssetList->BaseFileName + TEXT("_ue4mannequin"));
name_mesh = (FString(TEXT("SK_")) + vrmAssetList->BaseFileName + TEXT("_ue4mannequin"));
name_rig = (FString(TEXT("RIG_")) + vrmAssetList->BaseFileName + TEXT("_ue4mannequin"));
}
USkeleton *base = nullptr;
USkeletalMesh *ss = nullptr;
UNodeMappingContainer *rr = nullptr;
#if WITH_EDITOR
{
auto p = vrmAssetList->Package;
if (i == 0) {
ss = VRM4U_DuplicateObject<USkeletalMesh>(src_sk, p, *name_mesh);
base = VRM4U_DuplicateObject<USkeleton>(src_k, p, *name_skeleton);
if (src_rig) {
rr = VRM4U_NewObject<UNodeMappingContainer>(p, *name_rig, RF_Public | RF_Standalone);
}
}else {
ss = VRM4U_DuplicateObject<USkeletalMesh>(src_sk, p, *name_mesh);
base = VRM4U_DuplicateObject<USkeleton>(src_k, p, *name_skeleton);
if (src_rig) {
rr = VRM4U_NewObject<UNodeMappingContainer>(p, *name_rig, RF_Public | RF_Standalone);
}
}
}
#else
ss = const_cast<USkeletalMesh*>(src_sk);
base = const_cast<USkeleton*>(src_k);
if (i == 1) continue;
#endif
new_sk[i] = ss;
new_k[i] = base;
new_rig[i] = rr;
VRMSetSkeleton(ss, base);
VRMGetNodeMappingData(ss).Empty();
VRMGetNodeMappingData(ss).Add(rr);
if (i == 0) {
// org -> humanoid
renameToHumanoidBone(ss, vrmAssetList->VrmMetaObject, src_sk);
} else {
// humanoid -> ue4mannequin
renameToUE4Bone(ss, vrmAssetList->VrmMetaObject, src_sk);
}
ss->CalculateInvRefMatrices();
ss->CalculateExtendedBounds();
#if WITH_EDITORONLY_DATA
ss->ConvertLegacyLODScreenSize();
#if UE_VERSION_OLDER_THAN(4,20,0)
#else
ss->UpdateGenerateUpToData();
#endif
#endif
#if WITH_EDITORONLY_DATA
base->SetPreviewMesh(ss);
#endif
base->RecreateBoneTree(ss);
// retarget mode
for (int bon = 0; bon < base->GetReferenceSkeleton().GetRawBoneNum(); ++bon) {
auto type = src_k->GetBoneTranslationRetargetingMode(bon);
base->SetBoneTranslationRetargetingMode(bon, type);
}
if (i == 0) {
vrmAssetList->HumanoidSkeletalMesh = ss;
vrmAssetList->VrmHumanoidMetaObject->SkeletalMesh = ss;
} else {
vrmAssetList->UE4SkeletalMesh = ss;
vrmAssetList->VrmMannequinMetaObject->SkeletalMesh = ss;
}
}
// meta rename check
// 0: humanoid
// 1: ue4mannequin
for (int i = 0; i < 2; ++i) {
USkeleton* base = nullptr;
USkeletalMesh* ss = nullptr;
UNodeMappingContainer* rr = nullptr;
UVrmMetaObject* meta = nullptr;
if (i == 0) {
ss = vrmAssetList->HumanoidSkeletalMesh;
base = VRMGetSkeleton(ss);
meta = vrmAssetList->VrmHumanoidMetaObject;
} else {
ss = vrmAssetList->UE4SkeletalMesh;
base = VRMGetSkeleton(ss);
meta = vrmAssetList->VrmMannequinMetaObject;
}
int boneNo = 0;
TPair<FString, FString> prev;
for (auto& a : meta->humanoidBoneTable) {
if (ss==nullptr) break;
auto ind = VRMGetRefSkeleton(ss).FindBoneIndex(*(a.Value));
if (boneNo == 0) {
prev = a;
}
if (ind == INDEX_NONE && a.Value!="") {
// set prev bone name
a.Value = prev.Value;
}
prev = a;
++boneNo;
}
}
return true;
}
VrmConvertHumanoid::VrmConvertHumanoid()
{
}
VrmConvertHumanoid::~VrmConvertHumanoid()
{
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,957 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvertMetadata.h"
#include "VrmConvert.h"
#include "VRM4ULoaderLog.h"
#include "VrmAssetListObject.h"
#include "VrmMetaObject.h"
#include "VrmLicenseObject.h"
#include "Vrm1LicenseObject.h"
#if UE_VERSION_OLDER_THAN(4,26,0)
#include "AssetRegistryModule.h"
#else
#include "AssetRegistry/AssetRegistryModule.h"
#endif
#include "UObject/Package.h"
#include "UObject/UObjectGlobals.h"
#include "VrmJson.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>
bool VRMConverter::ValidateSchema() {
return jsonData.validateSchema();
}
bool VRMConverter::Init(const uint8* pFileData, size_t dataSize, const aiScene *pScene) {
aiData = pScene;
return InitJSON(pFileData, dataSize);
}
bool VRMConverter::InitJSON(const uint8* pFileData, size_t dataSize) {
// little endian
// skip size check
auto readData = [](const uint8* data, size_t offset) {
return static_cast<uint32_t>(data[offset]) |
(static_cast<uint32_t>(data[offset + 1]) << 8) |
(static_cast<uint32_t>(data[offset + 2]) << 16) |
(static_cast<uint32_t>(data[offset + 3]) << 24);
};
if (dataSize < 20 || pFileData[0] != 'g' || pFileData[1] != 'l' || pFileData[2] != 'T' || pFileData[3] != 'F') {
return false;
}
const uint32_t glTFversion = readData(pFileData, 4);
const uint32_t total_length = readData(pFileData, 8);
if (total_length != static_cast<uint32_t>(dataSize)) {
return false;
}
uint32_t jsonSize = 0;
if (glTFversion == 1) {
// glTF 1.0 GLB VRM的には不要だが念のため
const uint32_t content_length = readData(pFileData, 12);
const uint32_t content_format = readData(pFileData, 16);
if (content_format != 0) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("Content format is not JSON (glTF 1.0)"));
return false;
}
if (20 + content_length > dataSize) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("Content length exceeds file size"));
return false;
}
jsonSize = content_length;
} else if (glTFversion == 2) {
const uint32_t chunk_length = readData(pFileData, 12);
if (pFileData[16] != 'J' || pFileData[17] != 'S' || pFileData[18] != 'O' || pFileData[19] != 'N') {
UE_LOG(LogVRM4ULoader, Warning, TEXT("First chunk is not JSON"));
return false;
}
if (20 + chunk_length > dataSize) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("Chunk length exceeds file size"));
return false;
}
jsonSize = chunk_length;
}
else {
UE_LOG(LogVRM4ULoader, Warning, TEXT("glbVersion error"));
return false;
}
return jsonData.init(pFileData + 20, jsonSize);
}
static UVrmLicenseObject* tmpLicense0 = nullptr;
static UVrm1LicenseObject* tmpLicense1 = nullptr;
void VRMConverter::GetVRMMeta(const aiScene *mScenePtr, UVrmLicenseObject *& a, UVrm1LicenseObject *& b) {
tmpLicense0 = nullptr;
tmpLicense1 = nullptr;
ConvertVrmMeta(nullptr, mScenePtr, nullptr, 0);
a = tmpLicense0;
b = tmpLicense1;
}
bool VRMConverter::ConvertVrmFirst(UVrmAssetListObject* vrmAssetList, const uint8* pData, size_t dataSize) {
// material
if (VRMConverter::Options::Get().IsVRM10Model()) {
// mtoon params
vrmAssetList->MaterialHasMToon.Empty();
for (auto& mat : jsonData.doc["materials"].GetArray()) {
bool b = false;
if (mat.HasMember("extensions")) {
if (mat["extensions"].HasMember("VRMC_materials_mtoon")) {
b = true;
}
}
vrmAssetList->MaterialHasMToon.Add(b);
}
} else {
// alpha cutoff flag
vrmAssetList->MaterialHasAlphaCutoff.Empty();
if (jsonData.IsEnable()) {
for (auto& mat : jsonData.doc["materials"].GetArray()) {
bool b = false;
if (mat.HasMember("alphaCutoff")) {
b = true;
}
vrmAssetList->MaterialHasAlphaCutoff.Add(b);
}
}
}
return true;
}
bool VRMConverter::ConvertVrmMeta(UVrmAssetListObject* vrmAssetList, const aiScene* mScenePtr, const uint8* pData, size_t dataSize) {
//auto GetDataJSON = [](const RAPIDJSON_NAMESPACE::Value& root, std::initializer_list<const char*> path) -> std::optional<const RAPIDJSON_NAMESPACE::Value*> {
auto GetDataJSON = [](const RAPIDJSON_NAMESPACE::Value& root, std::initializer_list<const char*> path) -> std::pair<bool, const RAPIDJSON_NAMESPACE::Value*> {
const RAPIDJSON_NAMESPACE::Value* current = &root;
for (const char* key : path) {
if (current->IsObject() == false) {
return { false, nullptr };
}
auto itr = current->FindMember(key);
if (itr == current->MemberEnd()) {
return { false, nullptr };
}
current = &itr->value;
}
return { true, current };
};
tmpLicense0 = nullptr;
tmpLicense1 = nullptr;
if (mScenePtr == nullptr || mScenePtr->mVRMMeta == nullptr) {
UE_LOG(LogVRM4ULoader, Warning, TEXT("VRM metadata is missing from the imported scene."));
return false;
}
VRM::VRMMetadata* SceneMeta = reinterpret_cast<VRM::VRMMetadata*>(mScenePtr->mVRMMeta);
UVrmMetaObject* MetaObject = nullptr;
UVrmLicenseObject* lic0 = nullptr;
UVrm1LicenseObject* lic1 = nullptr;
{
UPackage* package = GetTransientPackage();
if (vrmAssetList) {
package = vrmAssetList->Package;
}
if (package == GetTransientPackage() || vrmAssetList == nullptr) {
MetaObject = VRM4U_NewObject<UVrmMetaObject>(package, NAME_None, EObjectFlags::RF_Public | RF_Transient, NULL);
if (VRMConverter::Options::Get().IsVRM10Model()) {
lic1 = VRM4U_NewObject<UVrm1LicenseObject>(package, NAME_None, EObjectFlags::RF_Public | RF_Transient, NULL);
} else {
lic0 = VRM4U_NewObject<UVrmLicenseObject>(package, NAME_None, EObjectFlags::RF_Public | RF_Transient, NULL);
}
}
else {
if (vrmAssetList->ReimportBase) {
MetaObject = vrmAssetList->ReimportBase->VrmMetaObject;
lic0 = vrmAssetList->ReimportBase->VrmLicenseObject;
lic1 = vrmAssetList->ReimportBase->Vrm1LicenseObject;
}
if (MetaObject == nullptr) {
MetaObject = VRM4U_NewObject<UVrmMetaObject>(package, *(FString(TEXT("VM_")) + vrmAssetList->BaseFileName + TEXT("_VrmMeta")), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
if (VRMConverter::Options::Get().IsVRM10Model()) {
if (lic1 == nullptr) {
lic1 = VRM4U_NewObject<UVrm1LicenseObject>(package, *(FString(TEXT("VL_")) + vrmAssetList->BaseFileName + TEXT("_Vrm1License")), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
}
else {
if (lic0 == nullptr) {
lic0 = VRM4U_NewObject<UVrmLicenseObject>(package, *(FString(TEXT("VL_")) + vrmAssetList->BaseFileName + TEXT("_VrmLicense")), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
}
}
}
if (MetaObject) MetaObject->MarkPackageDirty();
if (lic0) lic0->MarkPackageDirty();
if (lic1) lic1->MarkPackageDirty();
if (vrmAssetList) {
vrmAssetList->VrmMetaObject = MetaObject;
vrmAssetList->VrmLicenseObject = lic0;
vrmAssetList->Vrm1LicenseObject = lic1;
MetaObject->VrmAssetListObject = vrmAssetList;
}
else {
tmpLicense0 = lic0;
tmpLicense1 = lic1;
}
if (VRMConverter::Options::Get().IsVRM10Model()) {
}
}
if (SceneMeta == nullptr) {
return false;
}
MetaObject->Version = 0;
if (VRMConverter::Options::Get().IsVRM10Model()) {
MetaObject->Version = 1;
}
// bone
if (VRMConverter::Options::Get().IsVRM10Model()){
// VRM10
if (pData && dataSize) {
auto &humanBone = jsonData.doc["extensions"]["VRMC_vrm"]["humanoid"]["humanBones"];
auto &origBone = jsonData.doc["nodes"];
for (auto& g : humanBone.GetObject()) {
int node = g.value["node"].GetInt();
if (node >= 0 && node < (int)origBone.Size()) {
FString str = UTF8_TO_TCHAR(origBone[node]["name"].GetString());
if (VRMConverter::Options::Get().IsForceOriginalBoneName()) {
}else{
str = VRMUtil::MakeName(str, true);
}
MetaObject->humanoidBoneTable.Add(UTF8_TO_TCHAR(g.name.GetString())) = str;
} else {
MetaObject->humanoidBoneTable.Add(UTF8_TO_TCHAR(g.name.GetString())) = "";
}
}
}
} else {
for (auto& a : SceneMeta->humanoidBone) {
if (FString(a.humanBoneName.C_Str()) == "") {
continue;
}
FString str = UTF8_TO_TCHAR(a.nodeName.C_Str());
if (VRMConverter::Options::Get().IsForceOriginalBoneName()) {
} else {
str = VRMUtil::MakeName(str, true);
}
MetaObject->humanoidBoneTable.Add(UTF8_TO_TCHAR(a.humanBoneName.C_Str())) = str;
}
}
//shape
if (VRMConverter::Options::Get().IsVRM10Model()) {
// VRM10
auto &presets = jsonData.doc["extensions"]["VRMC_vrm"]["expressions"]["preset"];
MetaObject->BlendShapeGroup.SetNum(presets.Size());
int presetIndex = -1;
for (auto& presetData : presets.GetObject()){
++presetIndex;
MetaObject->BlendShapeGroup[presetIndex].name = UTF8_TO_TCHAR(presetData.name.GetString()); // ex happy
auto &bindData = presetData.value["morphTargetBinds"];
MetaObject->BlendShapeGroup[presetIndex].BlendShape.SetNum(bindData.Size());
MetaObject->BlendShapeGroup[presetIndex].isBinary = presetData.value["isBinary"].GetBool();
MetaObject->BlendShapeGroup[presetIndex].overrideBlink = presetData.value["overrideBlink"].GetString();
MetaObject->BlendShapeGroup[presetIndex].overrideLookAt = presetData.value["overrideLookAt"].GetString();
MetaObject->BlendShapeGroup[presetIndex].overrideMouth = presetData.value["overrideMouth"].GetString();
int bindIndex = -1;
for (auto &bind : bindData.GetArray()) {
++bindIndex;
//m->BlendShapeGroup[presetIndex].BlendShape[bindIndex].morphTargetName = UTF8_TO_TCHAR(aiGroup.bind[b].blendShapeName.C_Str());
auto &targetShape = MetaObject->BlendShapeGroup[presetIndex].BlendShape[bindIndex];
//targetShape.morphTargetName = UTF8_TO_TCHAR(aiGroup.bind[b].blendShapeName.C_Str());
//targetShape.meshName = UTF8_TO_TCHAR(aiGroup.bind[b].meshName.C_Str());
//targetShape.nodeName = UTF8_TO_TCHAR(aiGroup.bind[b].nodeName.C_Str());
//targetShape.weight = aiGroup.bind[b].weight;
targetShape.shapeIndex = bind["index"].GetInt();
{
int tmpNodeID = bind["node"].GetInt(); // adjust offset
auto& jsonNode = jsonData.doc["nodes"];
if (tmpNodeID >= 0 && tmpNodeID < (int)jsonNode.Size()) {
if (GetDataJSON(jsonNode.GetArray()[tmpNodeID], { "mesh" }).second){
int tmpMeshID = jsonNode.GetArray()[tmpNodeID]["mesh"].GetInt();
//meshID offset
int offset = 0;
for (int meshNo = 0; meshNo < tmpMeshID; ++meshNo) {
if (jsonData.doc["meshes"].GetArray()[meshNo].HasMember("primitives") == false) continue;
//offset += jsonData.doc["meshes"].GetArray()[meshNo]["primitives"].Size() - 1;
}
targetShape.meshID = tmpMeshID + offset;
}
}
}
if (targetShape.meshID < (int)jsonData.doc["meshes"].Size()) {
{
auto& targetNames = jsonData.doc["meshes"].GetArray()[targetShape.meshID]["extras"]["targetNames"];
if (targetShape.shapeIndex < (int)targetNames.Size()) {
targetShape.morphTargetName = UTF8_TO_TCHAR(targetNames.GetArray()[targetShape.shapeIndex].GetString());
}
}
{
auto& tmp = jsonData.doc["meshes"][targetShape.meshID]["primitives"]["extras"]["targetNames"];
if (targetShape.shapeIndex < (int)tmp.Size()) {
targetShape.morphTargetName = tmp[targetShape.shapeIndex].GetString();
if (VRMConverter::Options::Get().IsForceOriginalMorphTargetName()) {
}else{
targetShape.morphTargetName = VRMUtil::MakeName(targetShape.morphTargetName);
}
}
}
targetShape.meshName = UTF8_TO_TCHAR(jsonData.doc["meshes"].GetArray()[targetShape.meshID]["name"].GetString());
}
}
}
} else {
MetaObject->BlendShapeGroup.SetNum(SceneMeta->blendShapeGroupNum);
for (int i = 0; i < SceneMeta->blendShapeGroupNum; ++i) {
auto& aiGroup = SceneMeta->blendShapeGroup[i];
FString s = UTF8_TO_TCHAR(aiGroup.groupName.C_Str());
if (VRMConverter::Options::Get().IsRemoveBlendShapeGroupPrefix()) {
int32 ind = 0;
if (s.FindLastChar('.', ind)) {
if (ind < s.Len() - 1) {
s = s.RightChop(ind+1);
}
}
}
MetaObject->BlendShapeGroup[i].name = s;
MetaObject->BlendShapeGroup[i].BlendShape.SetNum(aiGroup.bindNum);
for (int b = 0; b < aiGroup.bindNum; ++b) {
auto& bind = MetaObject->BlendShapeGroup[i].BlendShape[b];
bind.morphTargetName = UTF8_TO_TCHAR(aiGroup.bind[b].blendShapeName.C_Str());
bind.meshName = UTF8_TO_TCHAR(aiGroup.bind[b].meshName.C_Str());
bind.nodeName = UTF8_TO_TCHAR(aiGroup.bind[b].nodeName.C_Str());
bind.weight = aiGroup.bind[b].weight;
bind.meshID = aiGroup.bind[b].meshID;
bind.shapeIndex = aiGroup.bind[b].shapeIndex;
if (VRMConverter::Options::Get().IsForceOriginalMorphTargetName()) {
}else{
bind.morphTargetName = VRMUtil::MakeName(bind.morphTargetName);
}
}
}
}
// tmp shape...
if (pData && vrmAssetList) {
if (VRMConverter::Options::Get().IsVRM10Model()) {
} else {
TMap<FString, FString> ParamTable;
ParamTable.Add("_Color", "mtoon_Color");
ParamTable.Add("_RimColor", "mtoon_RimColor");
ParamTable.Add("_EmisionColor", "mtoon_EmissionColor");
ParamTable.Add("_OutlineColor", "mtoon_OutColor");
auto groupp = GetDataJSON(jsonData.doc, {"extensions", "VRM", "blendShapeMaster", "blendShapeGroups"});
if (groupp.second) {
const auto &group = *(groupp.second);
for (int i = 0; i < (int)group.Size(); ++i) {
if (MetaObject->BlendShapeGroup.IsValidIndex(i) == false) break;
auto& bind = MetaObject->BlendShapeGroup[i];
auto& shape = group[i];
if (shape.HasMember("materialValues") == false) continue;
if (shape["materialValues"].IsArray() == false) continue;
for (auto& mat : shape["materialValues"].GetArray()) {
if (mat.IsObject() == false) continue;
if (mat.HasMember("materialName") == false || mat.HasMember("propertyName") == false || mat.HasMember("targetValue") == false) continue;
FVrmBlendShapeMaterialList mlist;
mlist.materialName = mat["materialName"].GetString();
mlist.propertyName = mat["propertyName"].GetString();
FString* tmp = vrmAssetList->MaterialNameOrigToAsset.Find(NormalizeFileName(mlist.materialName));
if (tmp == nullptr) {
continue;
}
mlist.materialName = *tmp;
if (ParamTable.Find(mlist.propertyName)) {
mlist.propertyName = ParamTable[mlist.propertyName];
}
mlist.color = FLinearColor(
mat["targetValue"].GetArray()[0].GetFloat(),
mat["targetValue"].GetArray()[1].GetFloat(),
mat["targetValue"].GetArray()[2].GetFloat(),
mat["targetValue"].GetArray()[3].GetFloat());
bind.MaterialList.Add(mlist);
}
}
}
}
}
if (VRMConverter::Options::Get().IsVRM10Model()) {
auto pp = GetDataJSON(jsonData.doc, { "extensions", "VRMC_springBone", "springs"});
if (pp.second) {
const auto& jsonSpring = *pp.second;
auto& sMeta = MetaObject->VRM1SpringBoneMeta.Springs;
sMeta.SetNum(jsonSpring.Size());
for (uint32 springNo = 0; springNo < jsonSpring.Size(); ++springNo) {
auto& jsonJoints = jsonSpring.GetArray()[springNo]["joints"];
auto& dstSpring = sMeta[springNo];
dstSpring.joints.SetNum(jsonJoints.Size());
for (uint32 jointNo = 0; jointNo < jsonJoints.Size(); ++jointNo) {
auto& jj = jsonJoints.GetArray()[jointNo];
auto& s = dstSpring.joints[jointNo];
s.dragForce = jj["dragForce"].GetFloat();
s.gravityPower = jj["gravityPower"].GetFloat();
if (jj.HasMember("gravityDir")) {
if (jj["gravityDir"].GetArray().Size() == 3) {
s.gravityDir.X = jj["gravityDir"][0].GetFloat();
s.gravityDir.Y = jj["gravityDir"][1].GetFloat();
s.gravityDir.Z = jj["gravityDir"][2].GetFloat();
}
}
int node = jj["node"].GetInt();
s.boneNo = -1;// node; // reset after bone optimize
{
auto& jsonNode = jsonData.doc["nodes"];
if (node >= 0 && node < (int)jsonNode.Size()) {
s.boneName = VRMUtil::GetSafeNewName(UTF8_TO_TCHAR(jsonNode[node]["name"].GetString()));
}
}
s.hitRadius = jj["hitRadius"].GetFloat();
s.stiffness = jj["stiffness"].GetFloat();
}
auto& jsonColliderGroups = jsonSpring.GetArray()[springNo]["colliderGroups"];
dstSpring.colliderGroups.SetNum(jsonColliderGroups.Size());
for (uint32 colNo = 0; colNo < jsonColliderGroups.Size(); ++colNo) {
dstSpring.colliderGroups[colNo] = jsonColliderGroups[colNo].GetInt();
}
}
auto& colMeta = MetaObject->VRM1SpringBoneMeta.Colliders;
auto& jsonColliders = jsonData.doc["extensions"]["VRMC_springBone"]["colliders"];
colMeta.SetNum(jsonColliders.Size());
for (int colNo = 0; colNo < (int)jsonColliders.Size(); ++colNo) {
auto& jsonCol = jsonColliders[colNo];
auto& cMeta = colMeta[colNo];
int node = jsonCol["node"].GetInt();
{
auto& jsonNode = jsonData.doc["nodes"];
if (node >= 0 && node < (int)jsonNode.Size()) {
cMeta.boneName = VRMUtil::GetSafeNewName(UTF8_TO_TCHAR(jsonNode[node]["name"].GetString()));
}
}
if (jsonCol["shape"].HasMember("sphere")) {
if (GetDataJSON(jsonCol, { "shape", "sphere", "offset" }).second && GetDataJSON(jsonCol, { "shape", "sphere", "radius" }).second) {
cMeta.offset.Set(
jsonCol["shape"]["sphere"]["offset"][0].GetFloat(),
jsonCol["shape"]["sphere"]["offset"][1].GetFloat(),
jsonCol["shape"]["sphere"]["offset"][2].GetFloat());
cMeta.radius = jsonCol["shape"]["sphere"]["radius"].GetFloat();
cMeta.shapeType = TEXT("sphere");
}
}
if (jsonCol["shape"].HasMember("capsule")) {
if (GetDataJSON(jsonCol, { "shape", "capsule", "offset" }).second && GetDataJSON(jsonCol, { "shape", "capsule", "radius" }).second && GetDataJSON(jsonCol, { "shape", "capsule", "tail" }).second) {
cMeta.offset.Set(
jsonCol["shape"]["capsule"]["offset"][0].GetFloat(),
jsonCol["shape"]["capsule"]["offset"][1].GetFloat(),
jsonCol["shape"]["capsule"]["offset"][2].GetFloat());
cMeta.radius = jsonCol["shape"]["capsule"]["radius"].GetFloat();
cMeta.tail.Set(
jsonCol["shape"]["capsule"]["tail"][0].GetFloat(),
jsonCol["shape"]["capsule"]["tail"][1].GetFloat(),
jsonCol["shape"]["capsule"]["tail"][2].GetFloat());
cMeta.shapeType = TEXT("capsule");
}
}
}
{
auto& cogMeta = MetaObject->VRM1SpringBoneMeta.ColliderGroups;
auto& jsonColliderGroups = jsonData.doc["extensions"]["VRMC_springBone"]["colliderGroups"];
cogMeta.SetNum(jsonColliderGroups.Size());
for (int cgNo = 0; cgNo < (int)jsonColliderGroups.Size(); ++cgNo) {
cogMeta[cgNo].name = jsonColliderGroups[cgNo].GetString();
for (int colNo = 0; colNo < (int)jsonColliderGroups[cgNo]["colliders"].Size(); ++colNo) {
cogMeta[cgNo].colliders.Add(jsonColliderGroups[cgNo]["colliders"][colNo].GetInt());
}
}
}
//sMeta.SetNum(jsonSpring.Size());
//auto& jsonColliderGroups = jsonSpring.GetArray()[springNo]["collidergroups"];
}
} else {
// spring
MetaObject->VRMSpringMeta.SetNum(SceneMeta->springNum);
for (int i = 0; i < SceneMeta->springNum; ++i) {
const auto& vrms = SceneMeta->springs[i];
auto& s = MetaObject->VRMSpringMeta[i];
s.stiffness = vrms.stiffness;
s.gravityPower = vrms.gravityPower;
s.gravityDir.Set(vrms.gravityDir[0], vrms.gravityDir[1], vrms.gravityDir[2]);
s.dragForce = vrms.dragForce;
s.hitRadius = vrms.hitRadius;
s.bones.SetNum(vrms.boneNum);
s.boneNames.SetNum(vrms.boneNum);
for (int b = 0; b < vrms.boneNum; ++b) {
s.bones[b] = vrms.bones[b];
s.boneNames[b] = UTF8_TO_TCHAR(vrms.bones_name[b].C_Str());
}
s.ColliderIndexArray.SetNum(vrms.colliderGourpNum);
for (int c = 0; c < vrms.colliderGourpNum; ++c) {
s.ColliderIndexArray[c] = vrms.colliderGroups[c];
}
}
//collider
MetaObject->VRMColliderMeta.SetNum(SceneMeta->colliderGroupNum);
for (int i = 0; i < SceneMeta->colliderGroupNum; ++i) {
const auto& vrmc = SceneMeta->colliderGroups[i];
auto& c = MetaObject->VRMColliderMeta[i];
c.bone = vrmc.node;
c.boneName = UTF8_TO_TCHAR(vrmc.node_name.C_Str());
c.collider.SetNum(vrmc.colliderNum);
for (int b = 0; b < vrmc.colliderNum; ++b) {
c.collider[b].offset = FVector(vrmc.colliders[b].offset[0], vrmc.colliders[b].offset[1], vrmc.colliders[b].offset[2]);
c.collider[b].radius = vrmc.colliders[b].radius;
}
}
}
//constraint
if (VRMConverter::Options::Get().IsVRM10Model()) {
// VRM10
auto& nodes = jsonData.doc["nodes"];
for (auto& node : nodes.GetArray()) {
auto pp = GetDataJSON(jsonData.doc, { "extensions", "VRMC_node_constraint", "constraint" });
if (!pp.second) continue;
auto& constraint = *pp.second;
if (constraint.HasMember("roll")) {
FVRMConstraintRoll c;
c.source = constraint["roll"]["source"].GetInt();
if (c.source < (int)nodes.Size()) {
c.sourceName = nodes.GetArray()[c.source]["name"].GetString();
}
c.rollAxis = constraint["roll"]["rollAxis"].GetString();
c.weight = constraint["roll"]["weight"].GetFloat();
FString n = node["name"].GetString();
if (VRMConverter::Options::Get().IsForceOriginalBoneName() == false) {
n = VRMUtil::MakeName(n, true);
c.sourceName = VRMUtil::MakeName(c.sourceName, true);
}
FVRMConstraint cc;
cc.constraintRoll = c;
cc.type = EVRMConstraintType::Roll;
MetaObject->VRMConstraintMeta.Add(n, cc);
}
if (constraint.HasMember("aim")) {
FVRMConstraintAim c;
c.source = constraint["aim"]["source"].GetInt();
if (c.source < (int)nodes.Size()) {
c.sourceName = nodes.GetArray()[c.source]["name"].GetString();
}
c.aimAxis = constraint["aim"]["aimAxis"].GetString();
c.weight = constraint["aim"]["weight"].GetFloat();
FString n = node["name"].GetString();
if (VRMConverter::Options::Get().IsForceOriginalBoneName() == false) {
n = VRMUtil::MakeName(n, true);
c.sourceName = VRMUtil::MakeName(c.sourceName, true);
}
FVRMConstraint cc;
cc.constraintAim = c;
cc.type = EVRMConstraintType::Aim;
MetaObject->VRMConstraintMeta.Add(n, cc);
}
if (constraint.HasMember("rotation")) {
FVRMConstraintRotation c;
c.source = constraint["rotation"]["source"].GetInt();
if (c.source < (int)nodes.Size()) {
c.sourceName = nodes.GetArray()[c.source]["name"].GetString();
}
c.weight = constraint["rotation"]["weight"].GetFloat();
FString n = node["name"].GetString();
if (VRMConverter::Options::Get().IsForceOriginalBoneName() == false) {
n = VRMUtil::MakeName(n, true);
c.sourceName = VRMUtil::MakeName(c.sourceName, true);
}
FVRMConstraint cc;
cc.constraintRotation = c;
cc.type = EVRMConstraintType::Rotation;
MetaObject->VRMConstraintMeta.Add(n, cc);
}
}
}
// vrma
if (VRMConverter::Options::Get().IsVRMAModel()) {
if (pData && dataSize) {
{
auto ppBone = GetDataJSON(jsonData.doc, { "extensions", "VRMC_vrm_animation", "humanoid", "humanBones" });
if (ppBone.second) {
auto& humanBone = *ppBone.second;
auto& origBone = jsonData.doc["nodes"];
for (auto& g : humanBone.GetObject()) {
int nodeNo = g.value["node"].GetInt();
if (FString(g.name.GetString()) == "") {
continue;
}
if (nodeNo >= 0 && nodeNo < (int)origBone.Size()) {
MetaObject->humanoidBoneTable.Add(UTF8_TO_TCHAR(g.name.GetString())) = UTF8_TO_TCHAR(origBone[nodeNo]["name"].GetString());
}
else {
MetaObject->humanoidBoneTable.Add(UTF8_TO_TCHAR(g.name.GetString())) = "";
}
}
}
}
{
auto ppPreset = GetDataJSON(jsonData.doc, { "extensions", "VRMC_vrm_animation", "expressions", "preset" });
if (ppPreset.second) {
auto& p = *ppPreset.second;
for (auto& m : p.GetObject()) {
FVRMAnimationExpressionPreset meta;
meta.expressionName = m.name.GetString();
meta.expressionNode = m.value["node"].GetInt();
meta.expressionNodeName = jsonData.doc["nodes"].GetArray()[meta.expressionNode]["name"].GetString();
vrmAssetList->VrmMetaObject->VRMAnimationMeta.expressionPreset.Add(meta);
}
}
}
{
auto pp = GetDataJSON(jsonData.doc, { "extensions", "VRMC_vrm_animation", "lookAt", "node" });
if (pp.second) {
auto& at = *pp.second;
if (at.IsInt()) {
vrmAssetList->VrmMetaObject->VRMAnimationMeta.lookAt.lookAtNode = at.GetInt();
}
}
}
}
}
// license
// license
if (VRMConverter::Options::Get().IsVRM10Model()) {
auto ppMeta = GetDataJSON(jsonData.doc, { "extensions", "VRMC_vrm_animation", "meta" });
if (ppMeta.second) {
auto& meta = *ppMeta.second;
for (auto m = meta.MemberBegin(); m != meta.MemberEnd(); ++m) {
FString key = UTF8_TO_TCHAR((*m).name.GetString());
if (key.Find("allow") == 0) {
FLicenseBoolDataPair p;
p.key = key;
p.value = (*m).value.GetBool();
lic1->LicenseBool.Add(p);
} else if (key == "thumbnailImage") {
if (vrmAssetList) {
int t = (*m).value.GetInt();
if (t >= 0 && t < vrmAssetList->Textures.Num()) {
lic1->thumbnail = vrmAssetList->Textures[t];
#if WITH_EDITORONLY_DATA
vrmAssetList->SmallThumbnailTexture = lic1->thumbnail;
#endif
}
}
} else {
if ((*m).value.IsArray()) {
int ind = 0;
bool bFound = false;
for (auto& a : lic1->LicenseStringArray) {
if (a.key != key) {
++ind;
continue;
}
bFound = true;
break;
}
if (bFound == false) {
ind = lic1->LicenseStringArray.AddDefaulted();
lic1->LicenseStringArray[ind].key = key;
}
for (auto& a : (*m).value.GetArray()) {
lic1->LicenseStringArray[ind].value.Add(UTF8_TO_TCHAR(a.GetString()));
}
} else {
FLicenseStringDataPair p;
p.key = key;
p.value = UTF8_TO_TCHAR((*m).value.GetString());
lic1->LicenseString.Add(p);
}
}
}
}
}else {
struct TT {
FString key;
FString &dst;
};
const TT table[] = {
{TEXT("version"), lic0->version},
{TEXT("author"), lic0->author},
{TEXT("contactInformation"), lic0->contactInformation},
{TEXT("reference"), lic0->reference},
// texture skip
{TEXT("title"), lic0->title},
{TEXT("allowedUserName"), lic0->allowedUserName},
{TEXT("violentUsageName"), lic0->violentUsageName},
{TEXT("sexualUsageName"), lic0->sexualUsageName},
{TEXT("commercialUsageName"), lic0->commercialUsageName},
{TEXT("otherPermissionUrl"), lic0->otherPermissionUrl},
{TEXT("licenseName"), lic0->licenseName},
{TEXT("otherLicenseUrl"), lic0->otherLicenseUrl},
{TEXT("violentUssageName"), lic0->violentUsageName},
{TEXT("sexualUssageName"), lic0->sexualUsageName},
{TEXT("commercialUssageName"), lic0->commercialUsageName},
};
for (int i = 0; i < SceneMeta->license.licensePairNum; ++i) {
auto &p = SceneMeta->license.licensePair[i];
for (auto &t : table) {
if (t.key == p.Key.C_Str()) {
t.dst = UTF8_TO_TCHAR(p.Value.C_Str());
}
}
if (vrmAssetList) {
if (FString(TEXT("texture")) == p.Key.C_Str()) {
int t = FCString::Atoi(*FString(p.Value.C_Str()));
if (t >= 0 && t < vrmAssetList->Textures.Num()) {
lic0->thumbnail = vrmAssetList->Textures[t];
}
}
}
}
}
return true;
}
bool VRMConverter::ConvertVrmMetaPost(UVrmAssetListObject* vrmAssetList, const aiScene* mScenePtr, const uint8* pData, size_t dataSize) {
//sort
if (vrmAssetList && vrmAssetList->VrmMetaObject){
auto& t = vrmAssetList->VrmMetaObject->humanoidBoneTable;
if (vrmAssetList->SkeletalMesh) {
auto& rk = VRMGetRefSkeleton(vrmAssetList->SkeletalMesh);
for (int i = 0; i < t.Num() - 1; ++i) {
t.ValueSort([&rk](const FString A, const FString B) {
return rk.FindBoneIndex(*A) < rk.FindBoneIndex(*B);
}
);
}
}
}
if (VRMConverter::Options::Get().IsGenerateHumanoidRenamedMesh()) {
UPackage* package = GetTransientPackage();
if (vrmAssetList) {
package = vrmAssetList->Package;
}
UVrmMetaObject* m = vrmAssetList->VrmMetaObject;
//m2 = VRM4U_NewObject<UVrmMetaObject>(package, *(FString(TEXT("VM_")) + vrmAssetList->BaseFileName + TEXT("_VrmMeta")), EObjectFlags::RF_Public | EObjectFlags::RF_Standalone);
{
TMap<FString, FString> BoneTable;
UVrmMetaObject* m2 = VRM4U_DuplicateObject<UVrmMetaObject>(m, package, *(FString(TEXT("VM_")) + vrmAssetList->BaseFileName + TEXT("_ue4mannequin_VrmMeta")));
vrmAssetList->VrmMannequinMetaObject = m2;
for (auto& a : m2->humanoidBoneTable) {
auto c = VRMUtil::table_ue4_vrm.FindByPredicate([a](const VRMUtil::VRMBoneTable& data) {
// meta humanoid key == utiltable vrmbone
return a.Key.ToLower() == data.BoneVRM.ToLower();
});
if (c) {
if (a.Value != "") {
BoneTable.Add(a.Value, c->BoneUE4);
a.Value = c->BoneUE4;
}
}
}
for (auto& a : m2->VRMColliderMeta) {
auto c = BoneTable.Find(a.boneName);
if (c) {
if (a.boneName != "") {
a.boneName = *c;
}
}
}
for (auto& a : m2->VRMSpringMeta) {
for (auto& b : a.boneNames) {
auto c = BoneTable.Find(b);
//auto c = VRMUtil::table_ue4_vrm.FindByPredicate([b](const VRMUtil::VRMBoneTable& data) { return b.ToLower() == data.BoneVRM.ToLower(); });
if (c) {
if (b != "") {
b = *c;
}
}
}
}
}
{
TMap<FString, FString> BoneTable;
UVrmMetaObject* m3 = VRM4U_DuplicateObject<UVrmMetaObject>(m, package, *(FString(TEXT("VM_")) + vrmAssetList->BaseFileName + TEXT("_humanoid_VrmMeta")));
vrmAssetList->VrmHumanoidMetaObject = m3;
for (auto& a : m3->humanoidBoneTable) {
if (a.Value != "") {
BoneTable.Add(a.Value, a.Key);
a.Value = a.Key;
}
}
for (auto& a : m3->VRMColliderMeta) {
auto c = BoneTable.Find(a.boneName);
if (c) {
if (a.boneName != "") {
a.boneName = *c;
}
}
}
for (auto& a : m3->VRMSpringMeta) {
for (auto& b : a.boneNames) {
auto c = BoneTable.Find(b);
//auto c = VRMUtil::table_ue4_vrm.FindByPredicate([b](const VRMUtil::VRMBoneTable& data) { return b.ToLower() == data.BoneVRM.ToLower(); });
if (c) {
if (b != "") {
b = *c;
}
}
}
}
}
}
// vrm1
if (vrmAssetList->SkeletalMesh){
auto& sk = vrmAssetList->SkeletalMesh;
auto& refSkeleton = VRMGetRefSkeleton(sk);
{
auto& sMeta = vrmAssetList->VrmMetaObject->VRM1SpringBoneMeta.Springs;
for (auto& s : sMeta) {
for (auto& j : s.joints) {
j.boneNo = refSkeleton.FindBoneIndex(*j.boneName);
}
}
}
{
auto& cMeta = vrmAssetList->VrmMetaObject->VRM1SpringBoneMeta.Colliders;
for (auto& s : cMeta) {
s.boneNo = refSkeleton.FindBoneIndex(*s.boneName);
}
}
}
return true;
}
VrmConvertMetadata::VrmConvertMetadata()
{
}
VrmConvertMetadata::~VrmConvertMetadata()
{
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,500 @@
// 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()
{
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,793 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvertRig.h"
#include "VrmConvert.h"
#include "VrmUtil.h"
#include "VrmAssetListObject.h"
#include "VrmMetaObject.h"
#include "LoaderBPFunctionLibrary.h"
#include "VrmBPFunctionLibrary.h"
#include "Engine/SkeletalMesh.h"
#include "RenderingThread.h"
#include "Rendering/SkeletalMeshModel.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "Rendering/SkeletalMeshLODRenderData.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "Animation/MorphTarget.h"
#include "Animation/NodeMappingContainer.h"
#include "Animation/Skeleton.h"
#include "Components/SkeletalMeshComponent.h"
#include "UObject/UnrealType.h"
#if UE_VERSION_OLDER_THAN(5,4,0)
#include "Animation/Rig.h"
#endif
#include "PhysicsEngine/PhysicsAsset.h"
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
#if UE_VERSION_OLDER_THAN(5,5,0)
#else
#include "PhysicsEngine/SkeletalBodySetup.h"
#endif
#include "VrmRigHeader.h"
#if WITH_EDITOR
#include "IPersonaToolkit.h"
#include "PersonaModule.h"
#include "Modules/ModuleManager.h"
#include "Animation/DebugSkelMeshComponent.h"
#endif
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/postprocess.h>
#include <assimp/GltfMaterial.h>
#include <assimp/vrm/vrmmeta.h>
//#include "Engine/.h"
namespace {
static int GetChildBoneLocal(const FReferenceSkeleton &skeleton, const int32 ParentBoneIndex, TArray<int32> & Children) {
Children.Reset();
//auto &r = skeleton->GetReferenceSkeleton();
auto &r = skeleton;
const int32 NumBones = r.GetRawBoneNum();
for (int32 ChildIndex = ParentBoneIndex + 1; ChildIndex < NumBones; ChildIndex++)
{
if (ParentBoneIndex == r.GetParentIndex(ChildIndex))
{
Children.Add(ChildIndex);
}
}
return Children.Num();
}
static bool isSameOrChild(const FReferenceSkeleton &skeleton, const int32 TargetBoneIndex, const int32 SameOrChildBoneIndex) {
auto &r = skeleton;
if (TargetBoneIndex < 0 || SameOrChildBoneIndex < 0) {
return false;
}
int32 c = SameOrChildBoneIndex;
for (int i = 0; i < skeleton.GetRawBoneNum(); ++i) {
if (c < 0) break;
if (c == TargetBoneIndex) {
return true;
}
c = skeleton.GetParentIndex(c);
}
return false;
}
}
bool VRMConverter::ConvertRig(UVrmAssetListObject *vrmAssetList) {
if (VRMConverter::Options::Get().IsDebugOneBone()) {
return true;
}
if (vrmAssetList->SkeletalMesh == nullptr) {
return false;
}
bool bPlay = false;
{
bool b1, b2, b3;
b1 = b2 = b3 = false;
UVrmBPFunctionLibrary::VRMGetPlayMode(b1, b2, b3);
bPlay = b1;
}
if (bPlay) {
}
// override bone table
if (VRMConverter::Options::Get().IsVRMAModel()) {
if (vrmAssetList->VrmMetaObject) {
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
t.Value = t.Key;
}
}
}
auto* k = VRMGetSkeleton(vrmAssetList->SkeletalMesh);
#if UE_VERSION_OLDER_THAN(4,20,0)
#elif UE_VERSION_OLDER_THAN(5,4,0)
#if WITH_EDITOR
UNodeMappingContainer* mc = nullptr;
#if UE_VERSION_OLDER_THAN(5,0,0)
{
FString name = FString(TEXT("RIG_")) + vrmAssetList->BaseFileName;
mc = VRM4U_NewObject<UNodeMappingContainer>(vrmAssetList->Package, *name, RF_Public | RF_Standalone);
}
VRMGetNodeMappingData(vrmAssetList->SkeletalMesh).Add(mc);
URig *EngineHumanoidRig = LoadObject<URig>(nullptr, TEXT("/Engine/EngineMeshes/Humanoid.Humanoid"), nullptr, LOAD_None, nullptr);
mc->SetSourceAsset(EngineHumanoidRig);
VRMGetSkeleton(vrmAssetList->SkeletalMesh)->SetRigConfig(EngineHumanoidRig);
mc->SetTargetAsset(vrmAssetList->SkeletalMesh);
mc->AddDefaultMapping();
#endif
{
const VRM::VRMMetadata *meta = reinterpret_cast<VRM::VRMMetadata*>(aiData->mVRMMeta);
auto func = [&](const FString &a, const FString b) {
if (mc == nullptr) return;
mc->AddMapping(*a, *b);
VRMGetSkeleton(vrmAssetList->SkeletalMesh)->SetRigBoneMapping(*a, *b);
};
auto func2 = [&](const FString &a, FName b) {
func(a, b.ToString());
};
if (meta) {
for (auto &t : VRMUtil::table_ue4_vrm) {
FString target = t.BoneVRM;
const FString &ue4 = t.BoneUE4;
if (ue4.Compare(TEXT("Root"), ESearchCase::IgnoreCase) == 0) {
auto &a = VRMGetSkeleton(vrmAssetList->SkeletalMesh)->GetReferenceSkeleton().GetRefBoneInfo();
target = a[0].Name.ToString();
}
if (target.Len() == 0) {
continue;
}
for (auto b : meta->humanoidBone) {
if (target.Compare(b.humanBoneName.C_Str()) != 0) {
continue;
}
target = b.nodeName.C_Str();
break;
}
func(ue4, target);
}
{
const TArray<FString> cc = {
TEXT("Root"),
TEXT("Pelvis"),
TEXT("spine_01"),
TEXT("spine_02"),
TEXT("spine_03"),
TEXT("neck_01"),
};
// find bone from child bone
for (int i = cc.Num() - 2; i > 0; --i) {
if (mc == nullptr) continue;
const auto &m = mc->GetNodeMappingTable();
{
const auto p0 = m.Find(*cc[i]);
if (p0) {
// map exist
continue;
}
}
const auto p = m.Find(*cc[i + 1]);
if (p == nullptr) {
// child none
continue;
}
const FName *parentRoot = nullptr;
for (int toParent = i - 1; toParent > 0; --toParent) {
parentRoot = m.Find(*cc[toParent]);
if (parentRoot) {
break;
}
}
if (parentRoot == nullptr) {
continue;
}
// find (child) p -> (parent)parentRoot
FString newTarget = parentRoot->ToString();
{
const int32 index = k->GetReferenceSkeleton().FindBoneIndex(*p);
const int32 indexParent = k->GetReferenceSkeleton().GetParentIndex(index);
const int32 indexRoot = k->GetReferenceSkeleton().FindBoneIndex(*parentRoot);
if (isSameOrChild(k->GetReferenceSkeleton(), indexRoot, indexParent)) {
newTarget = k->GetReferenceSkeleton().GetBoneName(indexParent).ToString();
}
}
func(cc[i], newTarget);
}
// set null -> parent bone
for (int i = 1; i < cc.Num(); ++i) {
if (mc == nullptr) continue;
const auto &m = mc->GetNodeMappingTable();
{
const auto p0 = m.Find(*cc[i]);
if (p0) {
// map exist
continue;
}
}
const auto pp = m.Find(*cc[i-1]);
if (pp == nullptr) {
// parent none
continue;
}
// map=nullptr, parent=exist
FString newTarget = pp->ToString();
{
int32 index = k->GetReferenceSkeleton().FindBoneIndex(*pp);
TArray<int32> child;
GetChildBoneLocal(k->GetReferenceSkeleton(), index, child);
if (child.Num() == 1) {
// use one child
// need neck check...
//newTarget = k->GetReferenceSkeleton().GetBoneName(child[0]).ToString();
}
}
func(cc[i], newTarget);
}
}
} else {
// BVH auto mapping
const auto &rSk = k->GetReferenceSkeleton(); //EngineHumanoidRig->GetSourceReferenceSkeleton();
TArray<FString> rBoneList;
{
for (auto &a : rSk.GetRawRefBoneInfo()) {
rBoneList.Add(a.Name.ToString());
}
}
TArray<FString> existTable;
int boneIndex = -1;
for (auto &b : rBoneList) {
++boneIndex;
if (b.Find(TEXT("neck")) >= 0) {
const FString t = TEXT("neck_01");
if (existTable.Find(t) >= 0) {
continue;
}
existTable.Add(t);
func(t, b);
const TArray<FString> cc = {
TEXT("spine_03"),
TEXT("spine_02"),
TEXT("spine_01"),
TEXT("Pelvis"),
TEXT("Root"),
};
TArray<FName> targetBone;
targetBone.SetNum(5);
// map
const int neckBoneIndex = boneIndex;
{
int p = neckBoneIndex;
for (int i = 0; i < 5; ++i) {
int p2 = rSk.GetParentIndex(p);
if (p2 >= 0) {
p = p2;
}
targetBone[i] = rSk.GetBoneName(p);
}
}
// upper chest skip
for (int i = 0; i < 5; ++i) {
if (targetBone[2] == targetBone[3]) {
targetBone[2] = targetBone[1];
targetBone[1] = targetBone[0];
}
}
// root
targetBone[cc.Num() - 1] = rSk.GetBoneName(0);
// pelvis, spine
{
int p = neckBoneIndex;
for (int i = 0; i < 100; ++i) {
p = rSk.GetParentIndex(p);
if (p <= 0) break;
int p2 = rSk.GetParentIndex(p);
if (p2 <= 0) break;
// pelvis
targetBone[cc.Num() - 2] = rSk.GetBoneName(p2);
// spine
targetBone[cc.Num() - 3] = rSk.GetBoneName(p);
}
}
//regist
for (int i = 0; i < 5; ++i) {
func2(cc[i], targetBone[i]);
}
}
if (b.Find(TEXT("head")) >= 0) {
const FString t = TEXT("head");
if (existTable.Find(t) >= 0) {
continue;
}
existTable.Add(t);
func(t, b);
}
if (b.Find(TEXT("hand")) >= 0) {
if (b.Find("r") >= 0) {
const FString t = TEXT("Hand_R");
if (existTable.Find(t) >= 0){
continue;
}
existTable.Add(t);
func(t, b);
int p = rSk.GetParentIndex(boneIndex);
if (p >= 0) {
func2(TEXT("lowerarm_r"), rSk.GetBoneName(p));
}
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("UpperArm_R"), rSk.GetBoneName(p));
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("clavicle_r"), rSk.GetBoneName(p));
}
}
{
// finger right
TArray<int32> childBone;
GetChildBoneLocal(rSk, boneIndex, childBone);
FString tt[6][2] = {
{"thumb", "thumb_01_r"},
{"index", "index_01_r"},
{"middle", "middle_01_r"},
{"ring", "ring_01_r"},
{"little", "pinky_01_r"}, // little
{"pinky", "pinky_01_r"}, // little
};
for (auto& c : childBone) {
auto name = rSk.GetBoneName(c);
for (int i = 0; i < 6; ++i) {
{
int tmp = name.ToString().Find(tt[i][0]);
if (tmp < 0) continue;
}
int ind = VRMUtil::ue4_humanoid_bone_list.Find(tt[i][1]);
if (ind < 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind], rSk.GetBoneName(c));
TArray<int32> tmp = { c };
GetChildBoneLocal(rSk, tmp[0], tmp);
if (tmp.Num() <= 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind + 1], rSk.GetBoneName(tmp[0]));
GetChildBoneLocal(rSk, tmp[0], tmp);
if (tmp.Num() <= 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind + 2], rSk.GetBoneName(tmp[0]));
}
}
}
}
if (b.Find("l") >= 0) {
const FString t = TEXT("Hand_L");
if (existTable.Find(t) >= 0) {
continue;
}
existTable.Add(t);
func(t, b);
int p = rSk.GetParentIndex(boneIndex);
if (p >= 0) {
func2(TEXT("lowerarm_l"), rSk.GetBoneName(p));
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("UpperArm_L"), rSk.GetBoneName(p));
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("clavicle_l"), rSk.GetBoneName(p));
}
}
}
{
// finger left
TArray<int32> childBone;
GetChildBoneLocal(rSk, boneIndex, childBone);
FString tt[6][2] = {
{"thumb", "thumb_01_l"},
{"index", "index_01_l"},
{"middle", "middle_01_l"},
{"ring", "ring_01_l"},
{"little", "pinky_01_l"}, // little
{"pinky", "pinky_01_l"}, // little
};
for (auto& c : childBone) {
auto name = rSk.GetBoneName(c);
for (int i = 0; i < 6; ++i) {
{
int tmp = name.ToString().Find(tt[i][0]);
if (tmp < 0) continue;
}
int ind = VRMUtil::ue4_humanoid_bone_list.Find(tt[i][1]);
if (ind < 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind], rSk.GetBoneName(c));
TArray<int32> tmp = { c };
GetChildBoneLocal(rSk, tmp[0], tmp);
if (tmp.Num() <= 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind + 1], rSk.GetBoneName(tmp[0]));
GetChildBoneLocal(rSk, tmp[0], tmp);
if (tmp.Num() <= 0) continue;
func2(VRMUtil::ue4_humanoid_bone_list[ind + 2], rSk.GetBoneName(tmp[0]));
}
}
}
}
}
if (b.Find(TEXT("foot")) >= 0) {
if (b.Find("r") >= 0) {
const FString t = TEXT("Foot_R");
if (existTable.Find(t) >= 0) {
continue;
}
existTable.Add(t);
func(t, b);
{
TArray<int32> c;
GetChildBoneLocal(rSk, boneIndex, c);
if (c.Num()) {
func2(TEXT("ball_r"), rSk.GetBoneName(c[0]));
}
}
int p = rSk.GetParentIndex(boneIndex);
if (p >= 0) {
func2(TEXT("calf_r"), rSk.GetBoneName(p));
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("Thigh_R"), rSk.GetBoneName(p));
}
}
}
if (b.Find("l") >= 0) {
const FString t = TEXT("Foot_L");
if (existTable.Find(t) >= 0) {
continue;
}
existTable.Add(t);
func(t, b);
{
TArray<int32> c;
GetChildBoneLocal(rSk, boneIndex, c);
if (c.Num()) {
func2(TEXT("ball_l"), rSk.GetBoneName(c[0]));
}
}
int p = rSk.GetParentIndex(boneIndex);
if (p >= 0) {
func2(TEXT("calf_l"), rSk.GetBoneName(p));
p = rSk.GetParentIndex(p);
if (p >= 0) {
func2(TEXT("Thigh_L"), rSk.GetBoneName(p));
}
}
}
}
{
// pmx bone map
for (const auto &t : VRMUtil::table_ue4_pmx) {
FString pmxBone;
VRMUtil::GetReplacedPMXBone(pmxBone, t.BoneVRM);
FString targetList[2] = {
pmxBone,
t.BoneVRM,
};
bool finish = false;
for (int i = 0; i < 2; ++i) {
FString target = targetList[i];// t.BoneVRM;
const FString& ue4 = t.BoneUE4;
auto ind = k->GetReferenceSkeleton().FindBoneIndex(*target);
if (ind != INDEX_NONE) {
func(ue4, target);
for (const auto& v : VRMUtil::table_ue4_vrm) {
if (v.BoneUE4 == t.BoneUE4) {
if (v.BoneVRM.IsEmpty()) {
continue;
}
finish = true;
break;
}
}
}
if (finish) break;
}// 2 loop
}
}// pmx map
}
}// map end
}
//mc->AddMapping
if (mc) {
mc->PostEditChange();
vrmAssetList->HumanoidRig = mc;
}
#endif // editor
#endif //420
if (VRMConverter::Options::Get().IsVRMAModel()) {
// for expression curve
for (int i = 0; i < k->GetReferenceSkeleton().GetRawBoneNum(); ++i) {
k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Animation);
}
if (vrmAssetList && vrmAssetList->VrmMetaObject) {
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
auto i = k->GetReferenceSkeleton().FindBoneIndex(*t.Value);
if (i != INDEX_NONE) {
if (t.Key.Compare(TEXT("hips"), ESearchCase::IgnoreCase) == 0) {
} else {
k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Skeleton);
}
}
}
}
}else{
FString PelvisBoneName;
if (vrmAssetList && vrmAssetList->VrmMetaObject) {
for (auto& t : vrmAssetList->VrmMetaObject->humanoidBoneTable) {
if (t.Key.Compare(TEXT("hips"), ESearchCase::IgnoreCase) == 0) {
PelvisBoneName = t.Value;
break;
}
}
}
int bone = -1;
for (int i = 0; i < k->GetReferenceSkeleton().GetRawBoneNum(); ++i) {
//const int32 BoneIndex = k->GetReferenceSkeleton().FindBoneIndex(InBoneName);
k->SetBoneTranslationRetargetingMode(i, EBoneTranslationRetargetingMode::Skeleton);
//FAssetNotifications::SkeletonNeedsToBeSaved(k);
if (k->GetReferenceSkeleton().GetBoneName(i).Compare(*PelvisBoneName) == 0) {
bone = i;
}
}
bool first = true;
while (bone >= 0) {
if (first) {
k->SetBoneTranslationRetargetingMode(bone, EBoneTranslationRetargetingMode::AnimationScaled);
}
else {
k->SetBoneTranslationRetargetingMode(bone, EBoneTranslationRetargetingMode::Animation);
}
first = false;
bone = k->GetReferenceSkeleton().GetParentIndex(bone);
}
{
FName n[] = {
TEXT("ik_foot_root"),
TEXT("ik_foot_l"),
TEXT("ik_foot_r"),
TEXT("ik_hand_root"),
TEXT("ik_hand_gun"),
TEXT("ik_hand_l"),
TEXT("ik_hand_r"),
};
for (auto& s : n) {
int32 ind = k->GetReferenceSkeleton().FindBoneIndex(s);
if (ind < 0) continue;
k->SetBoneTranslationRetargetingMode(ind, EBoneTranslationRetargetingMode::Animation, false);
}
}
if (VRMConverter::Options::Get().IsPMXModel()) {
// center to animscale
int32 ind = k->GetReferenceSkeleton().FindBoneIndex(*(VRMUtil::table_ue4_pmx[1].BoneVRM));
if (ind >= 0) {
k->SetBoneTranslationRetargetingMode(ind, EBoneTranslationRetargetingMode::AnimationScaled, false);
}
}
if (VRMConverter::Options::Get().IsBVHModel()) {
// 0 == root by vrm4u dummy
// 1 == root by model
k->SetBoneTranslationRetargetingMode(0, EBoneTranslationRetargetingMode::Animation, false);
k->SetBoneTranslationRetargetingMode(1, EBoneTranslationRetargetingMode::Animation, false);
}
if (VRMConverter::Options::Get().IsVRMModel() == false) {
k->SetBoneTranslationRetargetingMode(0, EBoneTranslationRetargetingMode::Animation, false);
}
}
// dummy Collision
if (vrmAssetList) {
const VRM::VRMMetadata* meta = reinterpret_cast<VRM::VRMMetadata*>(aiData->mVRMMeta);
USkeletalMesh* sk = vrmAssetList->SkeletalMesh;
UPhysicsAsset* pa = VRMGetPhysicsAsset(sk);
const FString dummy_target[] = {
TEXT("hips"),
TEXT("head"),
TEXT("rightHand"),
TEXT("leftHand"),
TEXT("leftMiddleDistal"),
TEXT("rightMiddleDistal"),
TEXT("rightFoot"),
TEXT("leftFoot"),
TEXT("leftToes"),
TEXT("rightToes"),
TEXT("rightLowerArm"),
TEXT("leftLowerArm"),
TEXT("rightLowerLeg"),
TEXT("leftLowerLeg"),
};
if (meta && pa) {
for (const auto& a : meta->humanoidBone) {
{
bool bFound = false;
for (auto& d : dummy_target) {
if (d.Compare(a.humanBoneName.C_Str()) == 0) {
bFound = true;
}
}
if (bFound == false) {
continue;
}
}
{
bool b = false;
for (const auto& bs : pa->SkeletalBodySetups) {
FString s = bs->BoneName.ToString();
if (s.Compare(a.nodeName.C_Str(), ESearchCase::IgnoreCase) == 0) {
b = true;
break;
}
}
if (b) {
continue;
}
}
const int targetBone = VRMGetRefSkeleton(sk).FindRawBoneIndex(a.nodeName.C_Str());
if (targetBone == INDEX_NONE) {
break;
}
USkeletalBodySetup* bs = nullptr;
int BodyIndex1 = -1;
bs = NewObject<USkeletalBodySetup>(pa, *(FString(TEXT("dummy_for_clip")) + a.humanBoneName.C_Str()), RF_Transactional);
FKAggregateGeom agg;
FKSphereElem SphereElem;
SphereElem.Center = FVector(0);
SphereElem.Radius = 1.f;// center.Size();// 1.f;
agg.SphereElems.Add(SphereElem);
SphereElem.SetName(TEXT("dummy_for_clip"));
bs->Modify();
bs->BoneName = a.nodeName.C_Str();
bs->AddCollisionFrom(agg);
bs->CollisionTraceFlag = CTF_UseSimpleAsComplex;
// newly created bodies default to simulating
bs->PhysicsType = PhysType_Kinematic; // fix
//bs->get
bs->CollisionReponse = EBodyCollisionResponse::BodyCollision_Disabled;
bs->DefaultInstance.InertiaTensorScale.Set(2, 2, 2);
bs->DefaultInstance.LinearDamping = 0.f;
bs->DefaultInstance.AngularDamping = 0.f;
bs->InvalidatePhysicsData();
bs->CreatePhysicsMeshes();
BodyIndex1 = pa->SkeletalBodySetups.Add(bs);
//break;
}
pa->UpdateBoundsBodiesArray();
pa->UpdateBodySetupIndexMap();
RefreshSkelMeshOnPhysicsAssetChange(sk);
#if WITH_EDITOR
pa->RefreshPhysicsAssetChange();
#endif
#if WITH_EDITOR
if (VRMConverter::IsImportMode()) {
pa->PostEditChange();
}
#endif
}
}// collistion
return true;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,294 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmJson.h"
#include "VrmConvert.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include "rapidjson/schema.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/error/en.h"
bool VRMIsValid(const uint8_t* pData, size_t size) {
VRMConverter j;
if (j.Init(pData, size, nullptr)) {
return j.ValidateSchema();
}
return false;
}
bool VRMIsVRM10(RAPIDJSON_NAMESPACE::Document &doc) {
if (doc.HasMember("extensions")) {
if (doc["extensions"].HasMember("VRMC_vrm")) {
return true;
}
if (doc["extensions"].HasMember("VRMC_vrm_animation")) {
return true;
}
}
return false;
}
bool VRMIsVRM10(const uint8_t* pData, size_t size) {
VRMConverter j;
if (j.Init(pData, size, nullptr) == false) {
return false;
}
RAPIDJSON_NAMESPACE::Document& doc = j.jsonData.doc;
//doc.Parse(&v[0]);
return VRMIsVRM10(doc);
}
FString GetPluginThirdpartyPath() {
std::string ret;
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin("VRM4U");
if (!Plugin.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Plugin not found!"));
return FString();
}
return Plugin->GetBaseDir() / TEXT("Thirdparty");
}
std::string ReadTextFileFromThirdparty(const FString& RelativePath)
{
std::string ret;
FString TextFilePath = GetPluginThirdpartyPath() / RelativePath;
// ファイルの存在確認
if (!FPaths::FileExists(TextFilePath))
{
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: no file: %s"), *TextFilePath);
return ret;
}
// テキストファイルを読み込む
FString FileContent;
TArray<uint8> data;
//if (FFileHelper::LoadFileToString(FileContent, *ThirdpartyPath))
if (FFileHelper::LoadFileToArray(data, *TextFilePath))
{
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: read: %s"), *TextFilePath);
return std::string(reinterpret_cast<const char*>(data.GetData()), data.Num());
} else
{
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: failed: %s"), *TextFilePath);
return ret;
}
}
// Schema Provider ($ref をファイルパスで解決)
class FileSchemaProvider : public RAPIDJSON_NAMESPACE::IRemoteSchemaDocumentProvider {
public:
FileSchemaProvider(const std::string& baseDir){
BaseDir_ = FString(UTF8_TO_TCHAR(baseDir.c_str()));
}
const RAPIDJSON_NAMESPACE::SchemaDocument* GetRemoteDocument(const char* uri, RAPIDJSON_NAMESPACE::SizeType length) override {
// file name (ex: "VRM.meta.schema.json")
FString UriStr(UTF8_TO_TCHAR(uri));
FString SchemaFileName = FPaths::GetCleanFilename(UriStr);
// path (ex: /Game/Schemas/vrm0/VRM.meta.schema.json)
FString FilePath = FPaths::Combine(BaseDir_, SchemaFileName);
auto* CachedSchema = SchemaCache_.Find(FilePath);
if (CachedSchema) {
return *CachedSchema;
}
TArray<uint8> FileContent;
if (FPaths::FileExists(FilePath)) {
FFileHelper::LoadFileToArray(FileContent, *FilePath);
}
if (FileContent.Num() == 0){
if (FilePath.Contains(TEXT("/glTF"))) {
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: Failed to load schema: %s"), *FilePath);
} else {
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: Failed to load schema: %s"), *FilePath);
}
return nullptr;
}
// to string
std::string SchemaStr(reinterpret_cast<const char*>(FileContent.GetData()), FileContent.Num());
// parse
auto Doc = std::make_unique<RAPIDJSON_NAMESPACE::Document>();
if (Doc->Parse(SchemaStr.c_str()).HasParseError()) {
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: parse error in %s: %s"), *FilePath,
UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(Doc->GetParseError())));
return nullptr;
}
// SchemaDocument
auto Schema = std::make_unique<RAPIDJSON_NAMESPACE::SchemaDocument>(*Doc, uri, length, this);
auto* Ptr = Schema.get();
SchemaCache_.Add(FilePath, Ptr);
DocCache_.Add(FilePath, std::move(Doc));
SchemaDocCache_.Add(FilePath, std::move(Schema));
return Ptr;
}
private:
FString BaseDir_;
TMap<FString, RAPIDJSON_NAMESPACE::SchemaDocument*> SchemaCache_;
TMap<FString, std::unique_ptr<RAPIDJSON_NAMESPACE::Document>> DocCache_;
TMap<FString, std::unique_ptr<RAPIDJSON_NAMESPACE::SchemaDocument>> SchemaDocCache_;
};
// VRM1
bool validateSchemaVRM1_internal(RAPIDJSON_NAMESPACE::Document& jsonDoc, const std::string& path, const std::string& fileExt) {
// ext check
// extなしなら通過
if (!jsonDoc.HasMember("extensions") || !jsonDoc["extensions"].HasMember(fileExt.c_str())) {
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: no extensions/%s"), UTF8_TO_TCHAR(fileExt.c_str()));
// ここは成功扱い
return true;
}
std::string filename = fileExt + ".schema.json";
std::string schemaStr = ReadTextFileFromThirdparty(FString(UTF8_TO_TCHAR((path + "/" + filename).c_str())));
// parse
RAPIDJSON_NAMESPACE::Document schemaDoc;
if (schemaDoc.Parse(schemaStr.c_str()).HasParseError()) {
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: validation failed at: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(jsonDoc.GetParseError())));
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
return false;
}
FileSchemaProvider provider(std::string(TCHAR_TO_UTF8(*(GetPluginThirdpartyPath() / UTF8_TO_TCHAR(path.c_str())))));
RAPIDJSON_NAMESPACE::SchemaDocument schema(schemaDoc, nullptr, 0, &provider);
RAPIDJSON_NAMESPACE::SchemaValidator validator(schema);
RAPIDJSON_NAMESPACE::Value& vrmExtension = jsonDoc["extensions"][fileExt.c_str()];
if (!vrmExtension.Accept(validator)) {
RAPIDJSON_NAMESPACE::StringBuffer sb;
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: Schema validation failed at: %s"), UTF8_TO_TCHAR(sb.GetString()));
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: nvalid keyword: %s"), UTF8_TO_TCHAR(validator.GetInvalidSchemaKeyword()));
return false;
}
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: valid %s"), UTF8_TO_TCHAR(fileExt.c_str()));
return true;
}
bool validateSchemaVRM1(RAPIDJSON_NAMESPACE::Document& jsonDoc) {
bool ret = true;
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_vrm-1.0/schema", "VRMC_vrm");
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_springBone-1.0/schema", "VRMC_springBone");
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_node_constraint-1.0/schema", "VRMC_node_constraint");
ret &= validateSchemaVRM1_internal(jsonDoc, "vrm_specification/vrm1/VRMC_vrm_animation-1.0/schema", "VRMC_vrm_animation");
return ret;
}
// VRM0
bool validateSchemaVRM0_internal(RAPIDJSON_NAMESPACE::Document &jsonDoc, const std::string& path, const std::string& filename) {
std::string schemaStr = ReadTextFileFromThirdparty(FString(UTF8_TO_TCHAR((path + "/" + filename).c_str())));
// parse
RAPIDJSON_NAMESPACE::Document schemaDoc;
if (schemaDoc.Parse(schemaStr.c_str()).HasParseError()) {
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: validation failed at: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(jsonDoc.GetParseError())));
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
return false;
}
// ext
if (!jsonDoc.HasMember("extensions") || !jsonDoc["extensions"].HasMember("VRM")) {
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: no extensions/vrm"));
//std::cerr << "Missing VRMC_vrm extension in JSON" << std::endl;
return false;
}
FileSchemaProvider provider(std::string( TCHAR_TO_UTF8(*(GetPluginThirdpartyPath() / UTF8_TO_TCHAR(path.c_str())))) );
RAPIDJSON_NAMESPACE::SchemaDocument schema(schemaDoc, nullptr, 0, &provider);
RAPIDJSON_NAMESPACE::SchemaValidator validator(schema);
RAPIDJSON_NAMESPACE::Value& vrmExtension = jsonDoc["extensions"]["VRM"];
if (!vrmExtension.Accept(validator)) {
// エラー詳細を出力
RAPIDJSON_NAMESPACE::StringBuffer sb;
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: Schema validation failed at: %s"), UTF8_TO_TCHAR(sb.GetString()));
UE_LOG(LogTemp, Error, TEXT("VRM4U_Schema: nvalid keyword: %s"), UTF8_TO_TCHAR(validator.GetInvalidSchemaKeyword()));
return false;
}
UE_LOG(LogTemp, Log, TEXT("VRM4U_Schema: valid /%s"), UTF8_TO_TCHAR(filename.c_str()));
return true;
}
bool validateSchemaVRM0(RAPIDJSON_NAMESPACE::Document& jsonDoc) {
return validateSchemaVRM0_internal(jsonDoc, "vrm_specification/vrm0/schema", "vrm.schema.json");
}
bool VrmJson::validateSchema() {
bEnable = false;
std::vector<char> s;
//std::string ss = ReadTextFileFromThirdparty(TEXT("vrm_specification/vrm0/schema/vrm.schema.json"));
//std::string ss = ReadTextFileFromThirdparty(TEXT("vrm_specification/vrm1/VRMC_vrm-1.0/schema/VRMC_vrm.schema.json"));
if (VRMIsVRM10(doc)) {
if (validateSchemaVRM1(doc)) {
// vrm1 ok
bEnable = true;
} else {
// error
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: VRM1 validation failed"));
}
} else {
if (validateSchemaVRM0(doc)) {
bEnable = true;
// vrm0 ok
} else {
// error
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: VRM0 validation failed"));
}
}
if (bEnable == false) {
doc.Clear();
}
return bEnable;
}
bool VrmJson::init(const uint8_t* pData, size_t size) {
bEnable = false;
std::vector<char> v;
v.resize(size);
memcpy(&v[0], pData, size);
v.push_back(0);
if (doc.Parse(&v[0]).HasParseError()) {
UE_LOG(LogTemp, Warning, TEXT("VRM4U_Schema: parse error: %s"), UTF8_TO_TCHAR(RAPIDJSON_NAMESPACE::GetParseError_En(doc.GetParseError())));
//std::cerr << "Schema parse error: " << RAPIDJSON_NAMESPACE::GetParseError_En(schemaDoc.GetParseError())
// << " at offset " << schemaDoc.GetErrorOffset() << std::endl;
return false;
}
bEnable = true;
return true;
}
@@ -0,0 +1,45 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
// ApplicationLifecycleComponent.cpp: Component to handle receiving notifications from the OS about application state (activated, suspended, termination, etc)
#include "VrmLoaderComponent.h"
#include "VrmAssetListObject.h"
#include "LoaderBPFunctionLibrary.h"
#include "Misc/CoreDelegates.h"
UVrmLoaderComponent::UVrmLoaderComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UVrmLoaderComponent::OnRegister()
{
Super::OnRegister();
}
void UVrmLoaderComponent::OnUnregister()
{
Super::OnUnregister();
}
bool UVrmLoaderComponent::LoadVRMFile(const UVrmAssetListObject *InVrmAsset, UVrmAssetListObject *&OutVrmAsset, FString filepath) {
return ULoaderBPFunctionLibrary::LoadVRMFileLocal(InVrmAsset, OutVrmAsset, filepath);
}
bool UVrmLoaderComponent::LoadVRMFileAsync(const UVrmAssetListObject *InVrmAsset, FString filepath) {
ULoaderBPFunctionLibrary::LoadVRMFileLocal(InVrmAsset, AssetList, filepath);
bResultQueue = true;
return true;
}
void UVrmLoaderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) {
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (bResultQueue) {
bResultQueue = false;
OnFinishLoad.Broadcast(AssetList);
}
}
@@ -0,0 +1,710 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmConvert.h"
#include "VRM4ULoaderLog.h"
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "Async/ParallelFor.h"
#include "Engine/Texture2D.h"
#include "TextureResource.h"
#include "Modules/ModuleManager.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "PixelFormat.h"
#include "RenderUtils.h"
/////
//test
#if UE_VERSION_OLDER_THAN(5,0,0)
#if WITH_EDITOR==0
#define VRM4U_UseLocalTGAHeader 1
#endif
#else
#define VRM4U_UseLocalTGAHeader 1
#endif
#ifndef VRM4U_UseLocalTGAHeader
#define VRM4U_UseLocalTGAHeader 0
#endif
#if VRM4U_UseLocalTGAHeader
#pragma pack(push,1)
struct FTGAFileHeader
{
uint8 IdFieldLength;
uint8 ColorMapType;
uint8 ImageTypeCode; // 2 for uncompressed RGB format
uint16 ColorMapOrigin;
uint16 ColorMapLength;
uint8 ColorMapEntrySize;
uint16 XOrigin;
uint16 YOrigin;
uint16 Width;
uint16 Height;
uint8 BitsPerPixel;
uint8 ImageDescriptor;
friend FArchive& operator<<(FArchive& Ar, FTGAFileHeader& H)
{
Ar << H.IdFieldLength << H.ColorMapType << H.ImageTypeCode;
Ar << H.ColorMapOrigin << H.ColorMapLength << H.ColorMapEntrySize;
Ar << H.XOrigin << H.YOrigin << H.Width << H.Height << H.BitsPerPixel;
Ar << H.ImageDescriptor;
return Ar;
}
};
#pragma pack(pop)
#endif
void DecompressTGA_RLE_32bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint8* ImageData = (uint8*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
uint32 Pixel = 0;
int32 RLERun = 0;
int32 RAWRun = 0;
for (int32 Y = TGA->Height - 1; Y >= 0; Y--) // Y-flipped.
{
for (int32 X = 0; X < TGA->Width; X++)
{
if (RLERun > 0)
{
RLERun--; // reuse current Pixel data.
} else if (RAWRun == 0) // new raw pixel or RLE-run.
{
uint8 RLEChunk = *(ImageData++);
if (RLEChunk & 0x80)
{
RLERun = (RLEChunk & 0x7F) + 1;
RAWRun = 1;
} else
{
RAWRun = (RLEChunk & 0x7F) + 1;
}
}
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
if (RAWRun > 0)
{
Pixel = *(uint32*)ImageData; // RGBA 32-bit dword.
ImageData += 4;
RAWRun--;
RLERun--;
}
// Store.
*((TextureData + Y * TGA->Width) + X) = Pixel;
}
}
}
void DecompressTGA_RLE_24bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint8* ImageData = (uint8*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
uint8 Pixel[4] = {};
int32 RLERun = 0;
int32 RAWRun = 0;
for (int32 Y = TGA->Height - 1; Y >= 0; Y--) // Y-flipped.
{
for (int32 X = 0; X < TGA->Width; X++)
{
if (RLERun > 0)
RLERun--; // reuse current Pixel data.
else if (RAWRun == 0) // new raw pixel or RLE-run.
{
uint8 RLEChunk = *(ImageData++);
if (RLEChunk & 0x80)
{
RLERun = (RLEChunk & 0x7F) + 1;
RAWRun = 1;
} else
{
RAWRun = (RLEChunk & 0x7F) + 1;
}
}
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
if (RAWRun > 0)
{
Pixel[0] = *(ImageData++);
Pixel[1] = *(ImageData++);
Pixel[2] = *(ImageData++);
Pixel[3] = 255;
RAWRun--;
RLERun--;
}
// Store.
*((TextureData + Y * TGA->Width) + X) = *(uint32*)&Pixel;
}
}
}
void DecompressTGA_RLE_16bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint16* ImageData = (uint16*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
uint16 FilePixel = 0;
uint32 TexturePixel = 0;
int32 RLERun = 0;
int32 RAWRun = 0;
for (int32 Y = TGA->Height - 1; Y >= 0; Y--) // Y-flipped.
{
for (int32 X = 0; X < TGA->Width; X++)
{
if (RLERun > 0)
RLERun--; // reuse current Pixel data.
else if (RAWRun == 0) // new raw pixel or RLE-run.
{
uint8 RLEChunk = *((uint8*)ImageData);
ImageData = (uint16*)(((uint8*)ImageData) + 1);
if (RLEChunk & 0x80)
{
RLERun = (RLEChunk & 0x7F) + 1;
RAWRun = 1;
} else
{
RAWRun = (RLEChunk & 0x7F) + 1;
}
}
// Retrieve new pixel data - raw run or single pixel for RLE stretch.
if (RAWRun > 0)
{
FilePixel = *(ImageData++);
RAWRun--;
RLERun--;
}
// Convert file format A1R5G5B5 into pixel format B8G8R8B8
TexturePixel = (FilePixel & 0x001F) << 3;
TexturePixel |= (FilePixel & 0x03E0) << 6;
TexturePixel |= (FilePixel & 0x7C00) << 9;
TexturePixel |= (FilePixel & 0x8000) << 16;
// Store.
*((TextureData + Y * TGA->Width) + X) = TexturePixel;
}
}
}
void DecompressTGA_32bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint32* ImageData = (uint32*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
for (int32 Y = 0; Y < TGA->Height; Y++)
{
FMemory::Memcpy(TextureData + Y * TGA->Width, ImageData + (TGA->Height - Y - 1) * TGA->Width, TGA->Width * 4);
}
}
void DecompressTGA_16bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint16* ImageData = (uint16*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
uint16 FilePixel = 0;
uint32 TexturePixel = 0;
for (int32 Y = TGA->Height - 1; Y >= 0; Y--)
{
for (int32 X = 0; X < TGA->Width; X++)
{
FilePixel = *ImageData++;
// Convert file format A1R5G5B5 into pixel format B8G8R8A8
TexturePixel = (FilePixel & 0x001F) << 3;
TexturePixel |= (FilePixel & 0x03E0) << 6;
TexturePixel |= (FilePixel & 0x7C00) << 9;
TexturePixel |= (FilePixel & 0x8000) << 16;
// Store.
*((TextureData + Y * TGA->Width) + X) = TexturePixel;
}
}
}
void DecompressTGA_24bpp(const FTGAFileHeader* TGA, uint32* TextureData)
{
uint8* IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
uint8* ColorMap = IdData + TGA->IdFieldLength;
uint8* ImageData = (uint8*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
uint8 Pixel[4];
for (int32 Y = 0; Y < TGA->Height; Y++)
{
for (int32 X = 0; X < TGA->Width; X++)
{
Pixel[0] = *((ImageData + (TGA->Height - Y - 1) * TGA->Width * 3) + X * 3 + 0);
Pixel[1] = *((ImageData + (TGA->Height - Y - 1) * TGA->Width * 3) + X * 3 + 1);
Pixel[2] = *((ImageData + (TGA->Height - Y - 1) * TGA->Width * 3) + X * 3 + 2);
Pixel[3] = 255;
*((TextureData + Y * TGA->Width) + X) = *(uint32*)&Pixel;
}
}
}
void DecompressTGA_8bpp(const FTGAFileHeader* TGA, uint8* TextureData)
{
const uint8* const IdData = (uint8*)TGA + sizeof(FTGAFileHeader);
const uint8* const ColorMap = IdData + TGA->IdFieldLength;
const uint8* const ImageData = (uint8*)(ColorMap + (TGA->ColorMapEntrySize + 4) / 8 * TGA->ColorMapLength);
int32 RevY = 0;
for (int32 Y = TGA->Height - 1; Y >= 0; --Y)
{
const uint8* ImageCol = ImageData + (Y * TGA->Width);
uint8* TextureCol = TextureData + (RevY++ * TGA->Width);
FMemory::Memcpy(TextureCol, ImageCol, TGA->Width);
}
}
bool DecompressTGA_helper(
const int dummy,
const FTGAFileHeader* TGA,
uint32*& TextureData,
const int32 TextureDataSize,
FFeedbackContext* Warn = nullptr)
{
if (TGA->ImageTypeCode == 10) // 10 = RLE compressed
{
// RLE compression: CHUNKS: 1 -byte header, high bit 0 = raw, 1 = compressed
// bits 0-6 are a 7-bit count; count+1 = number of raw pixels following, or rle pixels to be expanded.
if (TGA->BitsPerPixel == 32)
{
DecompressTGA_RLE_32bpp(TGA, TextureData);
} else if (TGA->BitsPerPixel == 24)
{
DecompressTGA_RLE_24bpp(TGA, TextureData);
} else if (TGA->BitsPerPixel == 16)
{
DecompressTGA_RLE_16bpp(TGA, TextureData);
} else
{
//Warn->Logf(ELogVerbosity::Error, TEXT("TGA uses an unsupported rle-compressed bit-depth: %u"), TGA->BitsPerPixel);
return false;
}
} else if (TGA->ImageTypeCode == 2) // 2 = Uncompressed RGB
{
if (TGA->BitsPerPixel == 32)
{
DecompressTGA_32bpp(TGA, TextureData);
} else if (TGA->BitsPerPixel == 16)
{
DecompressTGA_16bpp(TGA, TextureData);
} else if (TGA->BitsPerPixel == 24)
{
DecompressTGA_24bpp(TGA, TextureData);
} else
{
//Warn->Logf(ELogVerbosity::Error, TEXT("TGA uses an unsupported bit-depth: %u"), TGA->BitsPerPixel);
return false;
}
}
// Support for alpha stored as pseudo-color 8-bit TGA
else if (TGA->ColorMapType == 1 && TGA->ImageTypeCode == 1 && TGA->BitsPerPixel == 8)
{
DecompressTGA_8bpp(TGA, (uint8*)TextureData);
}
// standard grayscale
else if (TGA->ColorMapType == 0 && TGA->ImageTypeCode == 3 && TGA->BitsPerPixel == 8)
{
DecompressTGA_8bpp(TGA, (uint8*)TextureData);
} else
{
//Warn->Logf(ELogVerbosity::Error, TEXT("TGA is an unsupported type: %u"), TGA->ImageTypeCode);
return false;
}
// Flip the image data if the flip bits are set in the TGA header.
bool FlipX = (TGA->ImageDescriptor & 0x10) ? 1 : 0;
bool FlipY = (TGA->ImageDescriptor & 0x20) ? 1 : 0;
if (FlipY || FlipX)
{
TArray<uint8> FlippedData;
FlippedData.AddUninitialized(TextureDataSize);
int32 NumBlocksX = TGA->Width;
int32 NumBlocksY = TGA->Height;
int32 BlockBytes = TGA->BitsPerPixel == 8 ? 1 : 4;
uint8* MipData = (uint8*)TextureData;
for (int32 Y = 0; Y < NumBlocksY; Y++)
{
for (int32 X = 0; X < NumBlocksX; X++)
{
int32 DestX = FlipX ? (NumBlocksX - X - 1) : X;
int32 DestY = FlipY ? (NumBlocksY - Y - 1) : Y;
FMemory::Memcpy(
&FlippedData[(DestX + DestY * NumBlocksX) * BlockBytes],
&MipData[(X + Y * NumBlocksX) * BlockBytes],
BlockBytes
);
}
}
FMemory::Memcpy(MipData, FlippedData.GetData(), FlippedData.Num());
}
return true;
}
bool DecompressTGA(
const FTGAFileHeader* TGA,
VRMUtil::FImportImage& OutImage,
FFeedbackContext* Warn = nullptr)
{
if (TGA->ColorMapType == 1 && TGA->ImageTypeCode == 1 && TGA->BitsPerPixel == 8)
{
// Notes: The Scaleform GFx exporter (dll) strips all font glyphs into a single 8-bit texture.
// The targa format uses this for a palette index; GFx uses a palette of (i,i,i,i) so the index
// is also the alpha value.
//
// We store the image as PF_G8, where it will be used as alpha in the Glyph shader.
OutImage.Init2DWithOneMip(
TGA->Width,
TGA->Height,
TSF_G8);
OutImage.CompressionSettings = TC_Grayscale;
} else if (TGA->ColorMapType == 0 && TGA->ImageTypeCode == 3 && TGA->BitsPerPixel == 8)
{
// standard grayscale images
OutImage.Init2DWithOneMip(
TGA->Width,
TGA->Height,
TSF_G8);
OutImage.CompressionSettings = TC_Grayscale;
} else
{
if (TGA->ImageTypeCode == 10) // 10 = RLE compressed
{
if (TGA->BitsPerPixel != 32 &&
TGA->BitsPerPixel != 24 &&
TGA->BitsPerPixel != 16)
{
//Warn->Logf(ELogVerbosity::Error, TEXT("TGA uses an unsupported rle-compressed bit-depth: %u"), TGA->BitsPerPixel);
return false;
}
} else
{
if (TGA->BitsPerPixel != 32 &&
TGA->BitsPerPixel != 16 &&
TGA->BitsPerPixel != 24)
{
//Warn->Logf(ELogVerbosity::Error, TEXT("TGA uses an unsupported bit-depth: %u"), TGA->BitsPerPixel);
return false;
}
}
OutImage.Init2DWithOneMip(
TGA->Width,
TGA->Height,
TSF_BGRA8);
}
int32 TextureDataSize = OutImage.RawData.Num();
uint32* TextureData = (uint32*)OutImage.RawData.GetData();
return DecompressTGA_helper(0, TGA, TextureData, TextureDataSize, Warn);
}
UTexture2D* VRMLoaderUtil::CreateTextureFromImage(FString name, UPackage* package, const void* vBuffer, const size_t Length, bool bGenerateMips, bool bNormal, bool bGreenFlip) {
const char* Buffer = (const char*)vBuffer;
VRMUtil::FImportImage img;
if (VRMLoaderUtil::LoadImageFromMemory(Buffer, Length, img) == false) {
return nullptr;
}
UTexture2D *tex = CreateTexture(img.SizeX, img.SizeY, name, package);
if (tex == nullptr) {
return nullptr;
}
{
uint8* MipData = (uint8*)GetPlatformData(tex)->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
if (bGreenFlip) {
//for (int32 y = 0; y < img.SizeY; y++)
ParallelFor(img.SizeY, [&](int y) {
const uint8* c = img.RawData.GetData() + (y * img.SizeX * sizeof(FColor));
uint8* DestPtr = MipData + (y * img.SizeX * sizeof(FColor));
for (int32 x = 0; x < img.SizeX; x++)
{
DestPtr[0] = c[0];
DestPtr[1] = 255 - c[1];
DestPtr[2] = c[2];
DestPtr[3] = c[3];
DestPtr += 4;
c += 4;
}
});
}else{
FMemory::Memcpy(MipData, img.RawData.GetData(), img.RawData.Num());
}
/*
for (int32 y = 0; y < Height; y++)
{
const aiTexel* c = &(t.pcData[y * Width]);
uint8* DestPtr = &MipData[y * Width * sizeof(FColor)];
for (int32 x = 0; x < Width; x++)
{
*DestPtr++ = c->b;
*DestPtr++ = c->g;
*DestPtr++ = c->r;
*DestPtr++ = c->a;
c++;
}
}
*/
GetPlatformData(tex)->Mips[0].BulkData.Unlock();
}
// Set options
tex->SRGB = true;// bUseSRGB;
tex->CompressionSettings = TC_Default;
if (bNormal) {
tex->CompressionSettings = TC_Normalmap;
tex->SRGB = 0;
#if WITH_EDITORONLY_DATA
tex->CompressionNoAlpha = true;
#endif
}
tex->AddressX = TA_Wrap;
tex->AddressY = TA_Wrap;
if (VRMConverter::IsImportMode() == false) {
// skip updateresource. call manuary
//tex->UpdateResource();
return tex;
}
#if WITH_EDITORONLY_DATA
tex->CompressionNone = false;
tex->DeferCompression = true;
if (bGenerateMips && FMath::IsPowerOfTwo(img.SizeX) && FMath::IsPowerOfTwo(img.SizeY)) {
tex->MipGenSettings = TMGS_FromTextureGroup;
} else {
tex->MipGenSettings = TMGS_NoMipmaps;
}
{
// alpha check
bool noAlpha = true;
uint8 *p = img.RawData.GetData();
for (int y = 0; y < img.SizeY; ++y) {
for (int x = 0; x < img.SizeX; ++x) {
if (p[(x + y * img.SizeX) * 4 + 3] != 255) {
noAlpha = false;
break;
}
}
if (noAlpha == false) break;
}
tex->CompressionNoAlpha = noAlpha;
}
tex->Source.Init(img.SizeX, img.SizeY, 1, 1, ETextureSourceFormat::TSF_BGRA8, img.RawData.GetData());
//NewTexture2D->Source.Compress();
#endif
// Update the remote texture data
tex->UpdateResource();
#if WITH_EDITOR
tex->PostEditChange();
#endif
return tex;
}
UTexture2D* VRMLoaderUtil::CreateTexture(int32 InSizeX, int32 InSizeY, FString name, UPackage* package) {
auto format = PF_B8G8R8A8;
UTexture2D* NewTexture = NULL;
if (InSizeX > 0 && InSizeY > 0 &&
(InSizeX % GPixelFormats[format].BlockSizeX) == 0 &&
(InSizeY % GPixelFormats[format].BlockSizeY) == 0)
{
if (package == GetTransientPackage()) {
NewTexture = NewObject<UTexture2D>(GetTransientPackage(), NAME_None, EObjectFlags::RF_Public | RF_Transient);
} else {
TArray<UObject*> ret;
GetObjectsWithOuter(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();
//a->Rename(TEXT("aaaaaaa"));
//auto *q = Cast<USkeletalMesh>(a);
//if (q) {
// q->Materials.Empty();
//}
a->Rename(NULL, GetTransientPackage(), REN_DontCreateRedirectors | REN_NonTransactional | REN_ForceNoResetLoaders);
break;
}
}
NewTexture = VRM4U_NewObject<UTexture2D>(
// GetTransientPackage(),
package,
*name,
//RF_Transient
RF_Public | RF_Standalone
);
}
NewTexture->Modify();
#if WITH_EDITOR
NewTexture->PreEditChange(NULL);
#endif
SetPlatformData(NewTexture, new FTexturePlatformData());
GetPlatformData(NewTexture)->SizeX = InSizeX;
GetPlatformData(NewTexture)->SizeY = InSizeY;
GetPlatformData(NewTexture)->PixelFormat = format;
int32 NumBlocksX = InSizeX / GPixelFormats[format].BlockSizeX;
int32 NumBlocksY = InSizeY / GPixelFormats[format].BlockSizeY;
#if UE_VERSION_OLDER_THAN(4,23,0)
FTexture2DMipMap* Mip = new(NewTexture->PlatformData->Mips) FTexture2DMipMap();
#else
FTexture2DMipMap* Mip = new FTexture2DMipMap();
GetPlatformData(NewTexture)->Mips.Add(Mip);
#endif
Mip->SizeX = InSizeX;
Mip->SizeY = InSizeY;
Mip->BulkData.Lock(LOCK_READ_WRITE);
Mip->BulkData.Realloc(NumBlocksX * NumBlocksY * GPixelFormats[format].BlockBytes);
Mip->BulkData.Unlock();
} else
{
UE_LOG(LogVRM4ULoader, Warning, TEXT("Invalid parameters specified for UTexture2D::Create()"));
}
return NewTexture;
}
bool VRMLoaderUtil::LoadImageFromMemory(const void* vBuffer, const size_t Length, VRMUtil::FImportImage& OutImage) {
const char* Buffer = (const char*)vBuffer;
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
TArray<TSharedPtr<IImageWrapper> > ImageWrapperList = {
ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG),
ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG),
ImageWrapperModule.CreateImageWrapper(EImageFormat::BMP),
};
TSharedPtr<IImageWrapper> ImageWrapper;
for (auto& a : ImageWrapperList) {
if (a.IsValid() == false) continue;
if (a->SetCompressed(Buffer, Length) == false) continue;
ImageWrapper = a;
break;
}
if (ImageWrapper.IsValid()) {
#if UE_VERSION_OLDER_THAN(4,25,0)
#else
TArray<uint8> RawData;
#endif
const TArray<uint8>* pRawData = nullptr;
#if UE_VERSION_OLDER_THAN(4,25,0)
if (ImageWrapper.IsValid()) {
if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, pRawData) == false) return false;
}
#else
if (ImageWrapper.IsValid()) {
if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawData) == false) return false;
}
pRawData = &RawData;
#endif
const int Width = FMath::Max(ImageWrapper->GetWidth(), 1);
const int Height = FMath::Max(ImageWrapper->GetHeight(), 1);
OutImage.Init2DWithOneMip(Width, Height, TSF_BGRA8);
{
uint8* MipData = (uint8*)OutImage.RawData.GetData();
if (ImageWrapper.IsValid()) {
if (pRawData) {
FMemory::Memcpy(MipData, pRawData->GetData(), pRawData->Num());
} else {
for (int32 y = 0; y < Height; y++)
{
const uint8* c = pRawData->GetData() + (y * Width);
uint8* DestPtr = &MipData[y * Width * sizeof(FColor)];
for (int32 x = 0; x < Width; x++)
{
*DestPtr++ = c[0];
*DestPtr++ = c[1];
*DestPtr++ = c[2];
*DestPtr++ = c[3];
c++;
}
}
}
} else {
//int no = pmxTexNameList.Num();
//MipData[0] = (no & 1 ? 255 : 0);
//MipData[1] = (no & 2 ? 255 : 0);
//MipData[2] = (no & 4 ? 255 : 0);
//MipData[3] = 255;
}
}
return true;
}
//
// TGA
//
// Support for alpha stored as pseudo-color 8-bit TGA
const FTGAFileHeader* TGA = (FTGAFileHeader*)Buffer;
if (Length >= sizeof(FTGAFileHeader) &&
((TGA->ColorMapType == 0 && TGA->ImageTypeCode == 2) ||
// ImageTypeCode 3 is greyscale
(TGA->ColorMapType == 0 && TGA->ImageTypeCode == 3) ||
(TGA->ColorMapType == 0 && TGA->ImageTypeCode == 10) ||
(TGA->ColorMapType == 1 && TGA->ImageTypeCode == 1 && TGA->BitsPerPixel == 8)))
{
// Check the resolution of the imported texture to ensure validity
//if (!IsImportResolutionValid(TGA->Width, TGA->Height, bAllowNonPowerOfTwo, Warn))
//{
// return false;
//}
const bool bResult = DecompressTGA(TGA, OutImage);
if (bResult && OutImage.CompressionSettings == TC_Grayscale && TGA->ImageTypeCode == 3)
{
// default grayscales to linear as they wont get compression otherwise and are commonly used as masks
OutImage.SRGB = false;
}
return bResult;
}
return false;
}
@@ -0,0 +1,14 @@
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
#include "VrmSkeletalMesh.h"
#include "Rendering/SkeletalMeshLODRenderData.h"
#include "Rendering/SkeletalMeshRenderData.h"
void UVrmSkeletalMesh::Proc() {
}
@@ -0,0 +1,661 @@
// 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);
}
}
}
}