958 lines
30 KiB
C++
958 lines
30 KiB
C++
// 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()
|
|
{
|
|
}
|