Add Games File
This commit is contained in:
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, ¶m.OptionForRuntimeLoad, TexCount, 0);
|
||||
}
|
||||
if (SubCount == 2) {
|
||||
ConvTex(param.OutVrmAsset, localAsset.ScenePtr, ¶m.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "Engine/LatentActionManager.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "VrmConvert.h"
|
||||
#include "VrmUtil.h"
|
||||
#include "LoaderBPFunctionLibrary.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EPathType : uint8
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMeshInfo_VRM4U
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<FVector> Vertices;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<FVector> Normals;
|
||||
|
||||
//UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<uint32> Triangles;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<int32> Triangles2;
|
||||
|
||||
|
||||
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray< TArray<FVector2D> > UV0;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<FLinearColor> VertexColors;
|
||||
|
||||
// UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
// TArray<FProcMeshTangent> MeshTangents;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<FVector> Tangents;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
FTransform RelativeTransform;
|
||||
|
||||
TArray<bool> vertexUseFlag;
|
||||
TArray<uint32_t> vertexIndexOptTable;
|
||||
uint32_t useVertexCount = 0;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FReturnedData
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
bool bSuccess = false;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
int32 NumMeshes = 0;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
TArray<FMeshInfo_VRM4U> meshInfo;
|
||||
|
||||
//UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData")
|
||||
//TMap<struct aiMesh*, uint32_t> meshToIndex;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class VRM4ULOADER_API ULoaderBPFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U")
|
||||
static bool VRMSetLoadMaterialType(EVRMImportMaterialType type);
|
||||
|
||||
UFUNCTION(BlueprintCallable,Category="VRM4U", meta = (DynamicOutputParam = "OutVrmAsset"))
|
||||
static bool LoadVRMFile(const class UVrmAssetListObject *InVrmAsset, class UVrmAssetListObject *&OutVrmAsset, const FString filepath, const FImportOptionData &OptionForRuntimeLoad);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U", meta = (Latent, DynamicOutputParam = "OutVrmAsset", WorldContext = "WorldContextObject", LatentInfo = "LatentInfo"))
|
||||
static void LoadVRMFileAsync(const UObject* WorldContextObject, const class UVrmAssetListObject* InVrmAsset, class UVrmAssetListObject*& OutVrmAsset, const FString filepath, const FImportOptionData& OptionForRuntimeLoad, struct FLatentActionInfo LatentInfo);
|
||||
|
||||
|
||||
static bool LoadVRMFileLocal(const class UVrmAssetListObject* InVrmAsset, class UVrmAssetListObject*& OutVrmAsset, const FString filepath);
|
||||
|
||||
static bool LoadVRMFileFromMemoryDefaultOption(UVrmAssetListObject*& OutVrmAsset, const FString filepath, const uint8* pData, size_t dataSize);
|
||||
static bool LoadVRMFileFromMemory(const UVrmAssetListObject* InVrmAsset, UVrmAssetListObject*& OutVrmAsset, const FString filepath, const uint8* pFileData, size_t dataSize);
|
||||
|
||||
static void SetImportMode(bool bImportMode, class UPackage *package);
|
||||
|
||||
//static void SetCopySkeletalMeshAnimation(bool bImportMode, class UPackage *package);
|
||||
|
||||
///
|
||||
UFUNCTION(BlueprintCallable,Category="VRM4U")
|
||||
static bool VRMReTransformHumanoidBone(class USkeletalMeshComponent *targetHumanoidSkeleton, const class UVrmMetaObject *meta, const class USkeletalMeshComponent *displaySkeleton);
|
||||
|
||||
//static void ReTransformHumanoidBone(USkeleton *targetHumanoidSkeleton, const UVrmMetaObject *meta, const USkeleton *displaySkeleton) {
|
||||
|
||||
///
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U")
|
||||
static bool CopyPhysicsAsset(USkeletalMesh *dstMesh, const USkeletalMesh *srcMesh, bool bResetCollisionTransform=true);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U")
|
||||
static bool CopyVirtualBone(USkeletalMesh *dstMesh, const USkeletalMesh *srcMesh);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U")
|
||||
static bool CreateTailBone(USkeletalMesh *skeletalMesh, const TArray<FString> &boneName);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U", meta = (DynamicOutputParam = "RigIK"))
|
||||
static void VRMGenerateEpicSkeletonToHumanoidIKRig(USkeletalMesh* srcSkeletalMesh, UObject*& outRigIK, UObject*& outIKRetargeter, UObject* targetRigIK);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U", meta = (DynamicOutputParam = "RigIK"))
|
||||
static void VRMGenerateIKRetargeterPose(UObject * IKRetargeter, UObject* targetRigIK, UPoseAsset* targetPose);
|
||||
|
||||
static bool IsValidVRM4UFile(FString filepath);
|
||||
static void GetVRMMeta(FString filepath, UVrmLicenseObject*& a, UVrm1LicenseObject*& b);
|
||||
};
|
||||
@@ -0,0 +1,273 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5,4,0)
|
||||
#else
|
||||
#include "Misc/FieldAccessor.h"
|
||||
#endif
|
||||
|
||||
#include "VrmUtil.h"
|
||||
#include "VrmJson.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>
|
||||
|
||||
//#include "VrmConvert.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
struct aiScene;
|
||||
class UTexture2D;
|
||||
class UMaterialInterface;
|
||||
class USkeletalMesh;
|
||||
class UVrmAssetListObject;
|
||||
class UVrmLicenseObject;
|
||||
class UVrm1LicenseObject;
|
||||
class UPackage;
|
||||
|
||||
|
||||
class VRM4ULOADER_API VRMConverter {
|
||||
|
||||
bool InitJSON(const uint8* pData, size_t pFileDataSize);
|
||||
|
||||
public:
|
||||
|
||||
VrmJson jsonData;
|
||||
const aiScene* aiData = nullptr;
|
||||
|
||||
char* GetMatName(int matNo) const;
|
||||
char* GetMatShaderName(int matNo) const;
|
||||
int GetMatNum() const;
|
||||
int GetMatCullMode(int matNo) const;
|
||||
int GetMatZWrite(int matNo) const;
|
||||
|
||||
int GetThumbnailTextureIndex() const;
|
||||
|
||||
bool GetMatParam(VRM::VRMMaterial &m, int matNo) const;
|
||||
|
||||
|
||||
static bool IsImportMode();
|
||||
static void SetImportMode(bool bImportMode);
|
||||
static FString NormalizeFileName(const char *str);
|
||||
static FString NormalizeFileName(const FString &str);
|
||||
|
||||
static bool NormalizeBoneName(const aiScene *mScenePtr);
|
||||
|
||||
bool Init(const uint8* pFileData, size_t dataSize, const aiScene*);
|
||||
bool ValidateSchema();
|
||||
|
||||
bool ConvertTextureAndMaterial(UVrmAssetListObject *vrmAssetList);
|
||||
bool ConvertModel(UVrmAssetListObject *vrmAssetList);
|
||||
bool ConvertMorphTarget(UVrmAssetListObject *vrmAssetList);
|
||||
|
||||
void GetVRMMeta(const aiScene *mScenePtr, UVrmLicenseObject *& a, UVrm1LicenseObject *& b);
|
||||
bool ConvertVrmFirst(UVrmAssetListObject* vrmAssetList, const uint8* pData, size_t dataSize);
|
||||
bool ConvertVrmMeta(UVrmAssetListObject *vrmAssetList, const aiScene *mScenePtr, const uint8* pData, size_t dataSize);
|
||||
bool ConvertVrmMetaPost(UVrmAssetListObject* vrmAssetList, const aiScene* mScenePtr, const uint8* pData, size_t dataSize);
|
||||
|
||||
bool ConvertHumanoid(UVrmAssetListObject *vrmAssetList);
|
||||
bool ConvertRig(UVrmAssetListObject *vrmAssetList);
|
||||
bool ConvertIKRig(UVrmAssetListObject* vrmAssetList);
|
||||
bool ConvertPose(UVrmAssetListObject* vrmAssetList);
|
||||
|
||||
UPackage *CreatePackageFromImportMode(UPackage *p, const FString &name);
|
||||
|
||||
class VRM4ULOADER_API Options {
|
||||
public:
|
||||
static Options & Get();
|
||||
|
||||
const FImportOptionData *ImportOption = nullptr;
|
||||
void SetVrmOption(const FImportOptionData *p) {
|
||||
ImportOption = p;
|
||||
}
|
||||
|
||||
class USkeleton *GetSkeleton();
|
||||
bool IsSimpleRootBone() const;
|
||||
|
||||
bool IsActiveBone() const;
|
||||
|
||||
bool IsSkipPhysics() const;
|
||||
bool IsSkipRetargeter() const;
|
||||
|
||||
bool IsSkipNoMeshBone() const;
|
||||
|
||||
bool IsSkipMorphTarget() const;
|
||||
|
||||
bool IsEnableMorphTargetNormal() const;
|
||||
|
||||
bool IsForceOriginalMorphTargetName() const;
|
||||
|
||||
bool IsRemoveBlendShapeGroupPrefix() const;
|
||||
|
||||
bool IsRemoveRootBoneRotation() const;
|
||||
bool IsRemoveRootBonePosition() const;
|
||||
|
||||
bool IsVRM10RemoveLocalRotation() const;
|
||||
bool IsVRM10BindToRestPose() const;
|
||||
|
||||
bool IsVRM10Bindpose() const;
|
||||
|
||||
bool IsForceOriginalBoneName() const;
|
||||
bool IsGenerateHumanoidRenamedMesh() const;
|
||||
|
||||
bool IsGenerateIKBone() const;
|
||||
bool IsGenerateRigIK() const;
|
||||
|
||||
bool IsDebugIgnoreVRMValidation() const;
|
||||
bool IsDebugOneBone() const;
|
||||
bool IsDebugNoMesh() const;
|
||||
bool IsDebugNoMaterial() const;
|
||||
|
||||
bool IsMobileBone() const;
|
||||
|
||||
int GetBoneWeightInfluenceNum() const;
|
||||
|
||||
bool IsForceOpaque() const;
|
||||
bool IsForceTwoSided() const;
|
||||
|
||||
bool IsSingleUAssetFile() const;
|
||||
|
||||
bool IsDefaultGridTextureMode() const;
|
||||
bool IsBC7Mode() const;
|
||||
bool IsMipmapGenerateMode() const;
|
||||
|
||||
bool IsGenerateOutlineMaterial() const;
|
||||
bool IsMergeMaterial() const;
|
||||
|
||||
bool IsMergePrimitive() const;
|
||||
|
||||
bool IsOptimizeMaterial() const;
|
||||
|
||||
bool IsOptimizeVertex() const;
|
||||
|
||||
bool IsRemoveDegenerateTriangles() const;
|
||||
|
||||
void ClearModelType();
|
||||
|
||||
bool IsUE5Material() const;
|
||||
|
||||
bool IsVRMModel() const;
|
||||
bool IsVRM0Model() const;
|
||||
bool IsVRM10Model() const;
|
||||
|
||||
void SetVRM0Model(bool bVRM);
|
||||
void SetVRM10Model(bool bVRM);
|
||||
|
||||
bool IsVRMAModel() const;
|
||||
void SetVRMAModel(bool bVRMA);
|
||||
|
||||
bool IsBVHModel() const;
|
||||
void SetBVHModel(bool bBVH);
|
||||
|
||||
bool IsPMXModel() const;
|
||||
void SetPMXModel(bool bPMX);
|
||||
|
||||
bool IsNoMesh() const;
|
||||
void SetNoMesh(bool bNoMesh);
|
||||
|
||||
bool IsForceOverride() const;
|
||||
float GetModelScale() const;
|
||||
|
||||
float GetAnimationTranslateScale() const;
|
||||
|
||||
float GetAnimationPlayRateScale() const;
|
||||
|
||||
bool IsAPoseRetarget() const;
|
||||
|
||||
EVRMImportMaterialType GetMaterialType() const;
|
||||
void SetMaterialType(EVRMImportMaterialType type);
|
||||
};
|
||||
};
|
||||
|
||||
class VRM4ULOADER_API VRMLoaderUtil {
|
||||
public:
|
||||
static UTexture2D* CreateTexture(int32 InSizeX, int32 InSizeY, FString name, UPackage* package);
|
||||
static UTexture2D* CreateTextureFromImage(FString name, UPackage* package, const void* Buffer, const size_t Length, bool GenerateMip = false, bool bNormal = false, bool bNormalGreenFlip = false);
|
||||
|
||||
static bool LoadImageFromMemory(const void* Buffer, const size_t Length, VRMUtil::FImportImage& OutImage);
|
||||
};
|
||||
|
||||
|
||||
class VRM4ULOADER_API VrmConvert {
|
||||
public:
|
||||
VrmConvert();
|
||||
~VrmConvert();
|
||||
};
|
||||
|
||||
extern FString VRM4U_GetPackagePath(UPackage* Outer);
|
||||
extern UPackage* VRM4U_CreatePackage(UPackage* Outer, FName Name);
|
||||
|
||||
template< class T >
|
||||
T* VRM4U_NewObject(UObject* Outer, FName Name, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr) {
|
||||
UPackage* pkg = Cast<UPackage>(Outer);
|
||||
if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) {
|
||||
pkg = VRM4U_CreatePackage(pkg, Name);
|
||||
}
|
||||
decltype(auto) r = NewObject<T>(pkg, Name, Flags, Template, bCopyTransientsFromClassDefaults, InInstanceGraph);
|
||||
r->MarkPackageDirty();
|
||||
return r;
|
||||
}
|
||||
|
||||
template< class T >
|
||||
T* VRM4U_NewObject(UObject* Outer, UClass* Class, FName Name, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr) {
|
||||
UPackage* pkg = Cast<UPackage>(Outer);
|
||||
if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) {
|
||||
pkg = VRM4U_CreatePackage(pkg, Name);
|
||||
}
|
||||
decltype(auto) r = NewObject<T>(pkg, Class, Name, Flags, Template, bCopyTransientsFromClassDefaults, InInstanceGraph);
|
||||
r->MarkPackageDirty();
|
||||
return r;
|
||||
}
|
||||
|
||||
template< class T >
|
||||
T* VRM4U_DuplicateObject(const T *src, UPackage* Outer, FName Name) {
|
||||
UPackage* pkg = Outer;
|
||||
if (VRMConverter::Options::Get().IsSingleUAssetFile() == false) {
|
||||
pkg = VRM4U_CreatePackage(Outer, Name);
|
||||
}
|
||||
decltype(auto) r = DuplicateObject<T>(src, pkg, Name);
|
||||
r->MarkPackageDirty();
|
||||
return r;
|
||||
}
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5,4,0)
|
||||
extern UObject* VRM4U_StaticDuplicateObject(UObject const* SourceObject, UObject* DestOuter, const FName DestName = NAME_None, EObjectFlags FlagMask = RF_AllFlags, UClass* DestClass = nullptr, EDuplicateMode::Type DuplicateMode = EDuplicateMode::Normal, EInternalObjectFlags InternalFlagsMask = EInternalObjectFlags::AllFlags);
|
||||
#else
|
||||
extern UObject* VRM4U_StaticDuplicateObject(UObject const* SourceObject, UObject* DestOuter, const FName DestName = NAME_None, EObjectFlags FlagMask = RF_AllFlags, UClass* DestClass = nullptr, EDuplicateMode::Type DuplicateMode = EDuplicateMode::Normal, EInternalObjectFlags InternalFlagsMask = EInternalObjectFlags_AllFlags);
|
||||
#endif
|
||||
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5,0,0)
|
||||
template<typename T>
|
||||
FTexturePlatformData* GetPlatformData(T* t) {
|
||||
return t->PlatformData;
|
||||
}
|
||||
template<typename T, typename U>
|
||||
void SetPlatformData(T* t, U* u) {
|
||||
t->PlatformData = u;
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
TFieldPtrAccessor<FTexturePlatformData> GetPlatformData(T* t) {
|
||||
return t->GetPlatformData();
|
||||
}
|
||||
template<typename T, typename U>
|
||||
void SetPlatformData(T* t, U* u) {
|
||||
t->SetPlatformData(u);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertHumanoid
|
||||
{
|
||||
public:
|
||||
VrmConvertHumanoid();
|
||||
~VrmConvertHumanoid();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertIKRig
|
||||
{
|
||||
public:
|
||||
VrmConvertIKRig();
|
||||
~VrmConvertIKRig();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertMetadata
|
||||
{
|
||||
public:
|
||||
VrmConvertMetadata();
|
||||
~VrmConvertMetadata();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertModel
|
||||
{
|
||||
public:
|
||||
VrmConvertModel();
|
||||
~VrmConvertModel();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertMorphTarget
|
||||
{
|
||||
public:
|
||||
VrmConvertMorphTarget();
|
||||
~VrmConvertMorphTarget();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertRig
|
||||
{
|
||||
public:
|
||||
VrmConvertRig();
|
||||
~VrmConvertRig();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class VRM4ULOADER_API VrmConvertTexture
|
||||
{
|
||||
public:
|
||||
VrmConvertTexture();
|
||||
~VrmConvertTexture();
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#define RAPIDJSON_NAMESPACE vrm4u::local::rapid_json
|
||||
#define RAPIDJSON_NAMESPACE_BEGIN namespace vrm4u { namespace local { namespace rapid_json {
|
||||
#define RAPIDJSON_NAMESPACE_END } } }
|
||||
|
||||
#include <vector>
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
class VrmJson {
|
||||
|
||||
bool bEnable = false;
|
||||
public:
|
||||
RAPIDJSON_NAMESPACE::Document doc;
|
||||
|
||||
bool validateSchema();
|
||||
bool init(const uint8_t* pData, size_t size);
|
||||
|
||||
bool IsEnable() const{
|
||||
return bEnable;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
// ApplicationLifecycleComponent.: See FCoreDelegates for details
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "Misc/CoreDelegates.h"
|
||||
#include "VrmLoaderComponent.generated.h"
|
||||
|
||||
// A parallel enum to the temperature change severity enum in CoreDelegates
|
||||
// Note if you change this, then you must change the one in CoreDelegates
|
||||
/*
|
||||
UENUM(BlueprintType)
|
||||
enum class ETemperatureSeverityType : uint8
|
||||
{
|
||||
Unknown,
|
||||
Good,
|
||||
Bad,
|
||||
Serious,
|
||||
Critical,
|
||||
|
||||
NumSeverities,
|
||||
};
|
||||
*/
|
||||
//static_assert((int)ETemperatureSeverityType::NumSeverities == (int)FCoreDelegates::ETemperatureSeverity::NumSeverities, "TemperatureSeverity enums are out of sync");
|
||||
|
||||
class UVrmAssetListObject;
|
||||
|
||||
/** Component to handle receiving notifications from the OS about application state (activated, suspended, termination, etc). */
|
||||
UCLASS(ClassGroup=Utility, HideCategories=(Activation, "Components|Activation", Collision), meta=(BlueprintSpawnableComponent))
|
||||
class VRM4ULOADER_API UVrmLoaderComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
//DECLARE_DYNAMIC_MULTICAST_DELEGATE(FApplicationLifetimeDelegate);
|
||||
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTemperatureChangeDelegate , ETemperatureSeverityType, Severity);
|
||||
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLowPowerModeDelegate, bool, bInLowPowerMode);
|
||||
|
||||
//DECLARE_MULTICAST_DELEGATE_OneParam(FStaticOnDropFiles, FString);
|
||||
//static FStaticOnDropFiles StaticOnDropFilesDelegate;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFinishLoad, UVrmAssetListObject*, AssetList);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnFinishLoad OnFinishLoad;
|
||||
|
||||
UPROPERTY()
|
||||
UVrmAssetListObject *AssetList = nullptr;
|
||||
mutable bool bResultQueue = false;
|
||||
|
||||
public:
|
||||
void OnRegister() override;
|
||||
void OnUnregister() override;
|
||||
|
||||
//static const UVrmDropFilesComponent* getLatestActiveComponent() {
|
||||
//return s_LatestActiveComponent;
|
||||
//};
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U", meta = (DynamicOutputParam = "OutVrmAsset"))
|
||||
bool LoadVRMFile(const UVrmAssetListObject *InVrmAsset, UVrmAssetListObject *&OutVrmAsset, FString filepath);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "VRM4U", meta = (DynamicOutputParam = "OutVrmAsset"))
|
||||
bool LoadVRMFileAsync(const UVrmAssetListObject *InVrmAsset, FString filepath);
|
||||
|
||||
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
|
||||
|
||||
|
||||
private:
|
||||
/** Native handlers that get registered with the actual FCoreDelegates, and then proceed to broadcast to the delegates above */
|
||||
/*
|
||||
void ApplicationWillDeactivateDelegate_Handler() { ApplicationWillDeactivateDelegate.Broadcast(); }
|
||||
void ApplicationHasReactivatedDelegate_Handler() { ApplicationHasReactivatedDelegate.Broadcast(); }
|
||||
void ApplicationWillEnterBackgroundDelegate_Handler() { ApplicationWillEnterBackgroundDelegate.Broadcast(); }
|
||||
void ApplicationHasEnteredForegroundDelegate_Handler() { ApplicationHasEnteredForegroundDelegate.Broadcast(); }
|
||||
void ApplicationWillTerminateDelegate_Handler() { ApplicationWillTerminateDelegate.Broadcast(); }
|
||||
void ApplicationShouldUnloadResourcesDelegate_Handler() { ApplicationShouldUnloadResourcesDelegate.Broadcast(); }
|
||||
void ApplicationReceivedStartupArgumentsDelegate_Handler(const TArray<FString>& StartupArguments) { ApplicationReceivedStartupArgumentsDelegate.Broadcast(StartupArguments); }
|
||||
void OnTemperatureChangeDelegate_Handler(FCoreDelegates::ETemperatureSeverity Severity) { OnTemperatureChangeDelegate.Broadcast((ETemperatureSeverityType)Severity); }
|
||||
void OnLowPowerModeDelegate_Handler(bool bInLowerPowerMode) { OnLowPowerModeDelegate.Broadcast(bInLowerPowerMode); }
|
||||
*/
|
||||
//void OnDropFilesDelegate_Handler(FString FileName) { OnDropFiles.Broadcast(FileName); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "VrmSkeletalMesh.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS()
|
||||
class VRM4ULOADER_API UVrmSkeletalMesh : public USkeletalMesh
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
public:
|
||||
void Proc();
|
||||
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
//#include "VrmSkeleton.generated.h"
|
||||
|
||||
class USkeletalMesh;
|
||||
class UVrmAssetListObject;
|
||||
|
||||
class VRM4ULOADER_API VRMSkeleton {
|
||||
public:
|
||||
static void readVrmBone(struct aiScene* scene, int& boneOffset, FReferenceSkeleton& RefSkeleton, UVrmAssetListObject* assetList);
|
||||
|
||||
static void addIKBone(class UVrmAssetListObject* vrmAssetList, USkeletalMesh* sk);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
// VRM4U Copyright (c) 2021-2024 Haruyoshi Yamamoto. This software is released under the MIT License.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.IO;
|
||||
|
||||
|
||||
public class VRM4ULoader : ModuleRules
|
||||
{
|
||||
private string ModulePath
|
||||
{
|
||||
get { return ModuleDirectory; }
|
||||
}
|
||||
|
||||
private string ThirdPartyPath
|
||||
{
|
||||
get { return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty/")); }
|
||||
}
|
||||
|
||||
public VRM4ULoader(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
// for thirdparty header
|
||||
//bUseUnityBuild = false;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
Path.Combine(ThirdPartyPath, "assimp/include"),
|
||||
Path.Combine(ThirdPartyPath, "rapidjson/include")
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[] {
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"RHI",
|
||||
"RenderCore",
|
||||
"CinematicCamera",
|
||||
"AnimGraphRuntime",
|
||||
"Projects",
|
||||
"VRM4U",
|
||||
});
|
||||
PrivateDependencyModuleNames.Add("TimeManagement");
|
||||
|
||||
if (Target.bBuildEditor) {
|
||||
PrivateDependencyModuleNames.Add("Persona");
|
||||
}
|
||||
|
||||
BuildVersion Version;
|
||||
if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version))
|
||||
{
|
||||
//if (Version.MajorVersion == X && Version.MinorVersion == Y)
|
||||
if (Version.MajorVersion == 5)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("IKRig");
|
||||
if (Target.bBuildEditor)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("IKRigEditor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
}
|
||||
);
|
||||
|
||||
RuntimeDependencies.Add(Path.Combine(ThirdPartyPath, "vrm_specification", "vrm0", "schema", "*"));
|
||||
RuntimeDependencies.Add(Path.Combine(ThirdPartyPath, "vrm_specification", "vrm1", "*", "schema", "*"));
|
||||
|
||||
if (Target.bBuildEditor == true)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("UnrealEd");
|
||||
//PrivateDependencyModuleNames.Add("VRM4UImporter");
|
||||
//PrivateIncludePaths.Add("VRM4UImporter/Private");
|
||||
|
||||
//CircularlyReferencedDependentModules.Add("VRM4UImporter");
|
||||
}
|
||||
|
||||
if ((Target.Platform == UnrealTargetPlatform.Win64))
|
||||
{
|
||||
string PlatformString = (Target.Platform == UnrealTargetPlatform.Win64) ? "x64" : "x86";
|
||||
|
||||
bool bDebug = false;
|
||||
|
||||
if (bDebug){
|
||||
PublicDefinitions.Add("WITH_VRM4U_ASSIMP_DEBUG=1");
|
||||
|
||||
string BuildString = (Target.Configuration != UnrealTargetConfiguration.Debug) ? "Debug" : "Debug";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp", "lib", PlatformString, BuildString, "assimp-vc141-mtd.lib"));
|
||||
|
||||
PublicDelayLoadDLLs.Add("assimp-vc141-mtd.dll");
|
||||
RuntimeDependencies.Add(Path.Combine(ThirdPartyPath, "assimp", "bin", PlatformString, "assimp-vc141-mtd.dll"));
|
||||
}
|
||||
else
|
||||
{
|
||||
PublicDefinitions.Add("WITH_VRM4U_ASSIMP_DEBUG=0");
|
||||
|
||||
string BuildString = (Target.Configuration != UnrealTargetConfiguration.Debug) ? "Release" : "Release";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp", "lib", PlatformString, BuildString, "assimp-vc141-mt.lib"));
|
||||
|
||||
PublicDelayLoadDLLs.Add("assimp-vc141-mt.dll");
|
||||
RuntimeDependencies.Add(Path.Combine(ThirdPartyPath, "assimp", "bin", PlatformString, "assimp-vc141-mt.dll"));
|
||||
|
||||
}
|
||||
}
|
||||
if (Target.Platform == UnrealTargetPlatform.Android)
|
||||
{
|
||||
// static link
|
||||
{
|
||||
string PlatformString = "armeabi-v7a";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp", "lib", PlatformString, "libassimp.a"));
|
||||
}
|
||||
|
||||
/*
|
||||
// dynamic link
|
||||
{
|
||||
string PlatformString = "armeabi-v7a";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp/lib", PlatformString, "libassimp.so"));
|
||||
|
||||
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
|
||||
AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "VRM4ULoader_APL.xml"));
|
||||
}
|
||||
*/
|
||||
}
|
||||
if (Target.Platform == UnrealTargetPlatform.IOS)
|
||||
{
|
||||
string PlatformString = "IOS";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp", "lib", PlatformString, "libassimp.a"));
|
||||
}
|
||||
if (Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
// static lib
|
||||
string PlatformString = "Mac";
|
||||
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyPath, "assimp", "lib", PlatformString, "libassimp.a"));
|
||||
PublicSystemLibraries.Add("z");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--VRM4ULoader plugin additions-->
|
||||
<root xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- init section is always evaluated once per architecture -->
|
||||
<init>
|
||||
<log text="VRM4ULoader init"/>
|
||||
<setBool result="bSupported" value="false"/>
|
||||
<isArch arch="armeabi-v7a">
|
||||
<setBool result="bSupported" value="true"/>
|
||||
</isArch>
|
||||
</init>
|
||||
|
||||
<!-- optional files or directories to copy to Intermediate/Android/APK -->
|
||||
<resourceCopies>
|
||||
<isArch arch="armeabi-v7a">
|
||||
<copyFile src="$S(PluginDir)/../../ThirdParty/assimp/lib/armeabi-v7a/libassimp.so"
|
||||
dst="$S(BuildDir)/libs/armeabi-v7a/libassimp.so" />
|
||||
<log text="Copying libassimp.so"/>
|
||||
</isArch>
|
||||
<isArch arch="arm64-v8a">
|
||||
<copyFile src="$S(PluginDir)/ThirdParty/assimp/lib/arm64-v8a/libassimp.so"
|
||||
dst="$S(BuildDir)/libs/arm64-v8a/libassimp.so" />
|
||||
<log text="Copying libassimp.so"/>
|
||||
</isArch>
|
||||
</resourceCopies>
|
||||
|
||||
<!-- optional libraries to load in GameActivity.java before libUE4.so -->
|
||||
<soLoadLibrary>
|
||||
<if condition="bSupported">
|
||||
<true>
|
||||
<loadLibrary name="assimp" failmsg="assimp library not loaded and required!" />
|
||||
</true>
|
||||
</if>
|
||||
</soLoadLibrary>
|
||||
</root>
|
||||
Reference in New Issue
Block a user