From 47001c9ce37271d4ecc2f111aa2dfca15af5c02e Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 13 Apr 2019 15:30:55 +0100 Subject: [PATCH] import multiple animation files --- README.md | 5 ++++ src/FBX2glTF.cpp | 7 ++++- src/FBX2glTF.h | 3 ++ src/fbx/Fbx2Raw.cpp | 69 ++++++++++++++++++++++++++++++++++---------- src/fbx/Fbx2Raw.hpp | 6 +++- src/raw/RawModel.cpp | 9 ++++++ src/raw/RawModel.hpp | 1 + 7 files changed, 82 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 14992c5..70c8ddc 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Usage: from the FBX. --khr-materials-unlit Use KHR_materials_unlit extension to specify Unlit shader. + --animation-files Read multiple fbx animation files. --blend-shape-normals Include blend shape normals, if reported present by the FBX SDK. --blend-shape-tangents Include blend shape tangents, if reported @@ -75,6 +76,10 @@ Some of these switches are not obvious: Your FBX is likely constructed with the assumption that `(0, 0)` is bottom left, whereas glTF has `(0, 0)` as top left. To produce spec-compliant glTF, we must flip the texcoords. To request unflipped coordinates: +- '--animation-files' will try to read in additional animations from multiple + fbx files with numbers added to the base name (eg. test1.fbx, test2.fbx, ...). + It will apply animations to geometry in the base file by matching the node + names, so make sure all nodes have a unique name. - `--long-indices` lets you force the use of either 16-bit or 32-bit indices. The default option is auto, which make the choice on a per-mesh-size basis. - `--compute-normals` controls when automatic vertex normals should be computed diff --git a/src/FBX2glTF.cpp b/src/FBX2glTF.cpp index 270fcd7..5cc7be9 100644 --- a/src/FBX2glTF.cpp +++ b/src/FBX2glTF.cpp @@ -125,6 +125,11 @@ int main(int argc, char* argv[]) { [&](size_t count) { gltfOptions.useKHRLightsPunctual = (count == 0); }, "Don't use KHR_lights_punctual extension to export FBX lights."); + app.add_flag( + "--animation-files", + gltfOptions.readAnimationFiles, + "Read multiple fbx animation files."); + app.add_flag( "--user-properties", gltfOptions.enableUserProperties, @@ -302,7 +307,7 @@ int main(int argc, char* argv[]) { if (verboseOutput) { fmt::printf("Loading FBX File: %s\n", inputPath); } - if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg")) { + if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg", gltfOptions)) { fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath); return 1; } diff --git a/src/FBX2glTF.h b/src/FBX2glTF.h index 5d3f07a..7deeaae 100644 --- a/src/FBX2glTF.h +++ b/src/FBX2glTF.h @@ -95,6 +95,9 @@ struct GltfOptions /** Whether to include lights through the KHR_punctual_lights extension. */ bool useKHRLightsPunctual { true }; + /** Whether to read multiple fbx animation files. */ + bool readAnimationFiles { false }; + /** Whether to include blend shape normals, if present according to the SDK. */ bool useBlendShapeNormals { false }; /** Whether to include blend shape tangents, if present according to the SDK. */ diff --git a/src/fbx/Fbx2Raw.cpp b/src/fbx/Fbx2Raw.cpp index 9a11bdd..0a11d4e 100644 --- a/src/fbx/Fbx2Raw.cpp +++ b/src/fbx/Fbx2Raw.cpp @@ -721,7 +721,7 @@ static void ReadNodeHierarchy( } } -static void ReadAnimations(RawModel& raw, FbxScene* pScene) { +static void ReadAnimations(RawModel& raw, FbxScene* pScene, bool nodeNameLookup) { FbxTime::EMode eMode = FbxTime::eFrames24; const double epsilon = 1e-5f; @@ -772,7 +772,14 @@ static void ReadAnimations(RawModel& raw, FbxScene* pScene) { bool hasMorphs = false; RawChannel channel; - channel.nodeIndex = raw.GetNodeById(pNode->GetUniqueID()); + if (nodeNameLookup) { + channel.nodeIndex = raw.GetNodeByName(pNode->GetName()); + } else { + channel.nodeIndex = raw.GetNodeById(pNode->GetUniqueID()); + } + if (channel.nodeIndex == -1) { + continue; + } for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { FbxTime pTime; @@ -996,11 +1003,7 @@ static void FindFbxTextures( } } -bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions) { - FbxManager* pManager = FbxManager::Create(); - FbxIOSettings* pIoSettings = FbxIOSettings::Create(pManager, IOSROOT); - pManager->SetIOSettings(pIoSettings); - +static FbxScene* LoadScene(FbxManager* pManager, const char* fbxFileName) { FbxImporter* pImporter = FbxImporter::Create(pManager, ""); if (!pImporter->Initialize(fbxFileName, -1, pManager->GetIOSettings())) { @@ -1008,8 +1011,7 @@ bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExte fmt::printf("%s\n", pImporter->GetStatus().GetErrorString()); } pImporter->Destroy(); - pManager->Destroy(); - return false; + return nullptr; } FbxScene* pScene = FbxScene::Create(pManager, "fbxScene"); @@ -1017,14 +1019,9 @@ bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExte pImporter->Destroy(); if (pScene == nullptr) { - pImporter->Destroy(); - pManager->Destroy(); - return false; + return nullptr; } - std::map textureLocations; - FindFbxTextures(pScene, fbxFileName, textureExtensions, textureLocations); - // Use Y up for glTF FbxAxisSystem::MayaYUp.ConvertScene(pScene); @@ -1037,14 +1034,54 @@ bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExte if (sceneSystemUnit != FbxSystemUnit::cm) { FbxSystemUnit::cm.ConvertScene(pScene); } + + return pScene; +} + +bool LoadFBXFile( + RawModel& raw, + const char* fbxFileName, + const char* textureExtensions, + const GltfOptions& options) { + FbxManager* pManager = FbxManager::Create(); + FbxIOSettings* pIoSettings = FbxIOSettings::Create(pManager, IOSROOT); + pManager->SetIOSettings(pIoSettings); + + FbxScene* pScene = LoadScene(pManager, fbxFileName); + if (pScene == nullptr) { + pManager->Destroy(); + return false; + } + // this is always 0.01, but let's opt for clarity. scaleFactor = FbxSystemUnit::m.GetConversionFactorFrom(FbxSystemUnit::cm); + std::map textureLocations; + FindFbxTextures(pScene, fbxFileName, textureExtensions, textureLocations); + ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), 0, ""); ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureLocations); - ReadAnimations(raw, pScene); + ReadAnimations(raw, pScene, false); pScene->Destroy(); + + // read additional animation files + bool readAnimationFiles = options.readAnimationFiles; + int animationFileNumber = 1; + while (readAnimationFiles) { + std::string animationFileName = StringUtils::GetFolderString(fbxFileName) + + StringUtils::GetFileBaseString(fbxFileName) + + std::to_string(animationFileNumber) + ".fbx"; + FbxScene* pAnimationScene = LoadScene(pManager, animationFileName.c_str()); + if (pAnimationScene != nullptr) { + ReadAnimations(raw, pAnimationScene, true); + pAnimationScene->Destroy(); + animationFileNumber++; + } else { + readAnimationFiles = false; + } + } + pManager->Destroy(); return true; diff --git a/src/fbx/Fbx2Raw.hpp b/src/fbx/Fbx2Raw.hpp index a5fd231..117ae70 100644 --- a/src/fbx/Fbx2Raw.hpp +++ b/src/fbx/Fbx2Raw.hpp @@ -11,6 +11,10 @@ #include "raw/RawModel.hpp" -bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions); +bool LoadFBXFile( + RawModel& raw, + const char* fbxFileName, + const char* textureExtensions, + const GltfOptions& options); json TranscribeProperty(FbxProperty& prop); \ No newline at end of file diff --git a/src/raw/RawModel.cpp b/src/raw/RawModel.cpp index cafc076..b8c2497 100644 --- a/src/raw/RawModel.cpp +++ b/src/raw/RawModel.cpp @@ -641,6 +641,15 @@ int RawModel::GetNodeById(const long nodeId) const { return -1; } +int RawModel::GetNodeByName(const char* name) const { + for (size_t i = 0; i < nodes.size(); i++) { + if (nodes[i].name == name) { + return (int)i; + } + } + return -1; +} + int RawModel::GetSurfaceById(const long surfaceId) const { for (size_t i = 0; i < surfaces.size(); i++) { if (surfaces[i].id == surfaceId) { diff --git a/src/raw/RawModel.hpp b/src/raw/RawModel.hpp index c21c4bb..306eabf 100644 --- a/src/raw/RawModel.hpp +++ b/src/raw/RawModel.hpp @@ -509,6 +509,7 @@ class RawModel { return nodes[index]; } int GetNodeById(const long nodeId) const; + int GetNodeByName(const char* name) const; // Create individual attribute arrays. // Returns true if the vertices store the particular attribute.