diff --git a/src/fbx/Fbx2Raw.cpp b/src/fbx/Fbx2Raw.cpp index 6d78113..0c28498 100644 --- a/src/fbx/Fbx2Raw.cpp +++ b/src/fbx/Fbx2Raw.cpp @@ -916,38 +916,61 @@ static void ReadAnimations(RawModel& raw, FbxScene* pScene) { } } -static std::string GetInferredFileName( +static std::string FindFileLoosely( const std::string& fbxFileName, const std::string& directory, const std::vector& directoryFileList) { if (FileUtils::FileExists(fbxFileName)) { return fbxFileName; } - // Get the file name with file extension. + + // From e.g. C:/Assets/Texture.jpg, extract 'Texture.jpg' const std::string fileName = FileUtils::GetFileName(fbxFileName); // Try to find a match with extension. for (const auto& file : directoryFileList) { - if (StringUtils::CompareNoCase(fileName, file) == 0) { - return std::string(directory) + file; + if (StringUtils::CompareNoCase(fileName, FileUtils::GetFileName(file)) == 0) { + return directory + "/" + file; } } // Get the file name without file extension. const std::string fileBase = FileUtils::GetFileBase(fileName); - // Try to find a match without file extension. + // Try to find a match that ignores file extension for (const auto& file : directoryFileList) { - // If the two extension-less base names match. if (StringUtils::CompareNoCase(fileBase, FileUtils::GetFileBase(file)) == 0) { - // Return the name with extension of the file in the directory. - return std::string(directory) + file; + return directory + "/" + file; } } return ""; } +/** + * Try to locate the best match to the given texture filename, as provided in the FBX, + * possibly searching through the provided folders for a reasonable-looking match. + * + * Returns empty string if no match can be found, else the absolute path of the file. + **/ +static std::string FindFbxTexture( + const std::string& textureFileName, + const std::vector& folders, + const std::vector>& folderContents) { + // it might exist exactly as-is on the running machine's filesystem + if (FileUtils::FileExists(textureFileName)) { + return textureFileName; + } + // else look in other designated folders + for (int ii = 0; ii < folders.size(); ii++) { + const auto& fileLocation = FindFileLoosely(textureFileName, folders[ii], folderContents[ii]); + if (!fileLocation.empty()) { + return FileUtils::GetAbsolutePath(fileLocation); + } + } + return ""; +} + /* The texture file names inside of the FBX often contain some long author-specific path with the wrong extensions. For instance, all of the art assets may be PSD @@ -959,39 +982,45 @@ static std::string GetInferredFileName( */ static void FindFbxTextures( FbxScene* pScene, - const std::string fbxFileName, + const std::string& fbxFileName, const std::set& extensions, std::map& textureLocations) { - // Get the folder the FBX file is in. - const std::string folder = FileUtils::getFolder(fbxFileName); + // figure out what folder the FBX file is in, + const auto& fbxFolder = FileUtils::getFolder(fbxFileName); + std::vector folders{ + // first search filename.fbm folder which the SDK itself expands embedded textures into, + fbxFolder + "/" + FileUtils::GetFileBase(fbxFileName) + ".fbm", // filename.fbm + // then the FBX folder itself, + fbxFolder, + // then finally our working directory + FileUtils::GetCurrentFolder(), + }; - // Check if there is a filename.fbm folder to which embedded textures were extracted. - const std::string fbmFolderName = folder + FileUtils::GetFileBase(fbxFileName) + ".fbm/"; - - // Search either in the folder with embedded textures or in the same folder as the FBX file. - const std::string searchFolder = FileUtils::FolderExists(fbmFolderName) ? fbmFolderName : folder; - - // Get a list with all the texture files from either the folder with embedded textures or the same - // folder as the FBX file. - std::vector fileList = FileUtils::ListFolderFiles(searchFolder.c_str(), extensions); + // List the contents of each of these folders (if they exist) + std::vector> folderContents; + for (const auto& folder : folders) { + if (FileUtils::FolderExists(folder)) { + folderContents.push_back(FileUtils::ListFolderFiles(folder, extensions)); + } else { + folderContents.push_back({}); + } + } // Try to match the FBX texture names with the actual files on disk. for (int i = 0; i < pScene->GetTextureCount(); i++) { const FbxFileTexture* pFileTexture = FbxCast(pScene->GetTexture(i)); - if (pFileTexture == nullptr) { - continue; + if (pFileTexture != nullptr) { + const std::string fileLocation = + FindFbxTexture(pFileTexture->GetFileName(), folders, folderContents); + // always extend the mapping (even for files we didn't find) + textureLocations.emplace(pFileTexture, fileLocation.c_str()); + if (fileLocation.empty()) { + fmt::printf( + "Warning: could not find a image file for texture: %s.\n", pFileTexture->GetName()); + } else if (verboseOutput) { + fmt::printf("Found texture '%s' at: %s\n", pFileTexture->GetName(), fileLocation); + } } - const std::string inferredName = - GetInferredFileName(pFileTexture->GetFileName(), searchFolder, fileList); - if (inferredName.empty()) { - fmt::printf( - "Warning: could not find a local image file for texture: %s.\n" - "Original filename: %s\n", - pFileTexture->GetName(), - pFileTexture->GetFileName()); - } - // always extend the mapping, even for files we didn't find - textureLocations.emplace(pFileTexture, inferredName.c_str()); } } diff --git a/src/utils/File_Utils.cpp b/src/utils/File_Utils.cpp index aa3ffa8..d4c22ab 100644 --- a/src/utils/File_Utils.cpp +++ b/src/utils/File_Utils.cpp @@ -23,13 +23,19 @@ namespace FileUtils { std::vector ListFolderFiles( - const std::string folder, + std::string folder, const std::set& matchExtensions) { std::vector fileList; - for (const std::filesystem::directory_entry& entry : std::filesystem::directory_iterator(folder)) { - const std::filesystem::path& path = entry.path(); - if (matchExtensions.find(path.extension().string()) != matchExtensions.end()) { - fileList.push_back(path.string()); + if (folder.empty()) { + folder = "."; + } + for (const auto& entry : std::filesystem::directory_iterator(folder)) { + const auto& suffix = FileUtils::GetFileSuffix(entry.path()); + if (suffix.has_value()) { + const auto& suffix_str = StringUtils::ToLower(suffix.value()); + if (matchExtensions.find(suffix_str) != matchExtensions.end()) { + fileList.push_back(entry.path().filename().string()); + } } } return fileList; diff --git a/src/utils/File_Utils.hpp b/src/utils/File_Utils.hpp index dbf53bb..d418d9a 100644 --- a/src/utils/File_Utils.hpp +++ b/src/utils/File_Utils.hpp @@ -34,8 +34,12 @@ bool CopyFile( const std::string& dstFilename, bool createPath = false); +inline std::string GetAbsolutePath(const std::string& filePath) { + return std::filesystem::absolute(filePath).string(); +} + inline std::string GetCurrentFolder() { - return std::filesystem::current_path().string() + "/"; + return std::filesystem::current_path().string(); } inline bool FileExists(const std::string& filePath) { diff --git a/src/utils/String_Utils.cpp b/src/utils/String_Utils.cpp deleted file mode 100644 index 7e0dd2f..0000000 --- a/src/utils/String_Utils.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#include "String_Utils.hpp" - -#include - -namespace StringUtils { - -int CompareNoCase(const std::string& s1, const std::string& s2) { - return strncasecmp(s1.c_str(), s2.c_str(), std::max(s1.length(), s2.length())); -} - -} // namespace StringUtils diff --git a/src/utils/String_Utils.hpp b/src/utils/String_Utils.hpp index d7691d8..2f261ef 100644 --- a/src/utils/String_Utils.hpp +++ b/src/utils/String_Utils.hpp @@ -9,6 +9,8 @@ #pragma once +#include +#include #include #include #include @@ -16,11 +18,15 @@ #if defined(_MSC_VER) #define strncasecmp _strnicmp -#define strcasecmp _stricmp #endif namespace StringUtils { +inline std::string ToLower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](uint8_t c) { return std::tolower(c); }); + return s; +} + inline int CompareNoCase(const std::string& s1, const std::string& s2) { return strncasecmp(s1.c_str(), s2.c_str(), std::max(s1.length(), s2.length())); }