From 17d46bfd7ac0e6536edcb10c2ec029fa9ff3b3c8 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Mon, 1 Apr 2019 17:05:16 -0700 Subject: [PATCH] Embrace Conan, use it to grab boost::filesystem. With this, we are able to get rid of all the increasingly broken file system utility code, and trust boost::filesystem to handle all the cross-platform complexity. The first version of this PR centred around C++17 & std::filesystem, but support remains too elusive; it seems works out of the box in Visual Studio (especially 2019), but is entirely missing from the Mac dclang, and even with GCC 8.0 it requires an explicit '-l c++fs'. Luckily the std:: version is almost exactly the boost:: version (not surprising) so when the world's caught up, we can ditch Boost and go all stdlib. Setting up Conan requires a bit of work; we'll want to document the details in the README. --- CMakeLists.txt | 54 ++++----- README.md | 112 +++++++++-------- conanfile.py | 18 +++ src/FBX2glTF.cpp | 29 ++--- src/FBX2glTF.h | 120 +++++++++--------- src/fbx/Fbx2Raw.cpp | 111 +++++++++++------ src/fbx/Fbx2Raw.hpp | 5 +- src/fbx/FbxBlendShapesAccess.hpp | 2 +- src/fbx/FbxSkinningAccess.hpp | 6 +- src/gltf/GltfModel.cpp | 6 +- src/gltf/GltfModel.hpp | 4 +- src/gltf/Raw2Gltf.cpp | 88 +++++++------- src/gltf/Raw2Gltf.hpp | 2 +- src/gltf/TextureBuilder.cpp | 25 ++-- src/gltf/properties/AnimationData.cpp | 2 +- src/gltf/properties/MaterialData.cpp | 12 +- src/gltf/properties/PrimitiveData.hpp | 2 +- src/mathfu.hpp | 2 + src/utils/File_Utils.cpp | 168 ++++---------------------- src/utils/File_Utils.hpp | 48 +++++++- src/utils/String_Utils.cpp | 80 ------------ src/utils/String_Utils.hpp | 36 ++---- 22 files changed, 417 insertions(+), 515 deletions(-) create mode 100644 conanfile.py delete mode 100644 src/utils/String_Utils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4e434b..66ae4aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,15 @@ cmake_minimum_required(VERSION 3.5) project(FBX2glTF) +set(typical_usage_str + "Example usage:\n\ + > mkdir -p build_debug\n\ + > conan install . -i build_debug -s build_type=Debug -e FBXSDK_SDKS=/home/zell/FBXSDK\n\ + > conan build . -bf build_debug") + if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR - "Building from within the source tree is not supported.\n" - "Hint: mkdir -p build; cmake -H. -Bbuild; make -Cbuild\n") + "Building from within the source tree is not supported! ${typical_usage_str}") endif () set(CMAKE_CXX_STANDARD 11) @@ -12,6 +17,11 @@ set(CMAKE_CXX_STANDARD 11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}") include(ExternalProject) +if(NOT EXISTS "${CMAKE_BINARY_DIR}/Findboost_filesystem.cmake") + message(FATAL_ERROR + "The Conan package manager must run ('install') first. ${typical_usage_str}") +endif() + # FBX foreach (FBXSDK_VERSION "2019.2") find_package(FBX) @@ -32,6 +42,11 @@ set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) find_package(Iconv QUIET) +# stuff we get from Conan +find_package(boost_filesystem REQUIRED) +find_package(boost_optional REQUIRED) +find_package(fmt REQUIRED) + # create a compilation database for e.g. clang-tidy set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -50,13 +65,11 @@ if (WIN32) if (NOT ZLIB_LIBRARIES) message(FATAL_ERROR "Cannot find zlib.lib in the expected location.") endif() - else() find_package(LibXml2 REQUIRED) find_package(ZLIB REQUIRED) endif() - # DRACO ExternalProject_Add(Draco GIT_REPOSITORY https://github.com/google/draco @@ -110,22 +123,6 @@ ExternalProject_Add(CPPCodec ) set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec") -# FMT -ExternalProject_Add(Fmt - PREFIX fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt - GIT_TAG 4.0.0 - CMAKE_CACHE_ARGS "-DFMT_DOC:BOOL=OFF" "-DFMT_INSTALL:BOOL=ON" "-DFMT_TEST:BOOL=OFF" - CMAKE_ARGS - -DCMAKE_INSTALL_PREFIX= -) -set(FMT_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fmt/include") -if (WIN32) - set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/fmt.lib") -else() - set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/libfmt.a") -endif() - if (APPLE) find_library(CF_FRAMEWORK CoreFoundation) message("CoreFoundation Framework: ${CF_FRAMEWORK}") @@ -190,7 +187,6 @@ set(LIB_SOURCE_FILES src/utils/File_Utils.hpp src/utils/Image_Utils.cpp src/utils/Image_Utils.hpp - src/utils/String_Utils.cpp src/utils/String_Utils.hpp third_party/CLI11/CLI11.hpp ) @@ -205,7 +201,6 @@ add_dependencies(libFBX2glTF MathFu FiFoMap CPPCodec - Fmt ) if (NOT MSVC) @@ -222,8 +217,10 @@ endif() target_link_libraries(libFBX2glTF ${FRAMEWORKS} + boost_filesystem::boost_filesystem + boost_optional::boost_optional + fmt::fmt ${DRACO_LIB} - ${FMT_LIB} optimized ${FBXSDK_LIBRARY} debug ${FBXSDK_LIBRARY_DEBUG} ${CMAKE_DL_LIBS} @@ -244,20 +241,21 @@ else() ) endif() -target_include_directories(libFBX2glTF SYSTEM PUBLIC +target_include_directories(libFBX2glTF PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src - "third_party/stb" - "third_party/json" + boost_filesystem::boost_filesystem + boost_optional::boost_optional + fmt::fmt ) target_include_directories(libFBX2glTF SYSTEM PUBLIC - Iconv::Iconv + "third_party/stb" + "third_party/json" ${FBXSDK_INCLUDE_DIR} ${DRACO_INCLUDE_DIR} ${MATHFU_INCLUDE_DIRS} ${FIFO_MAP_INCLUDE_DIR} ${CPPCODEC_INCLUDE_DIR} - ${FMT_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS} ) diff --git a/README.md b/README.md index 14992c5..00ea066 100644 --- a/README.md +++ b/README.md @@ -28,47 +28,64 @@ Or perhaps, as part of a more complex pipeline: You can always run the binary with --help to see what options it takes: ``` -FBX2glTF 2.0: Generate a glTF 2.0 representation of an FBX model. -Usage: - FBX2glTF [OPTION...] [] +FBX2glTF 0.9.6: Generate a glTF 2.0 representation of an FBX model. +Usage: FBX2glTF [OPTIONS] [FBX Model] - -i, --input arg The FBX model to convert. - -o, --output arg Where to generate the output, without suffix. - -e, --embed Inline buffers as data:// URIs within - generated non-binary glTF. - -b, --binary Output a single binary format .glb file. - -d, --draco Apply Draco mesh compression to geometries. - --flip-u Flip all U texture coordinates. - --flip-v Flip all V texture coordinates (default - behaviour!) - --no-flip-v Suppress the default flipping of V texture - coordinates - --pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes - from the FBX. - --khr-materials-unlit Use KHR_materials_unlit extension to specify - Unlit shader. - --blend-shape-normals Include blend shape normals, if reported - present by the FBX SDK. - --blend-shape-tangents Include blend shape tangents, if reported - present by the FBX SDK. - --long-indices arg Whether to use 32-bit indices - (never|auto|always). - --compute-normals arg When to compute normals for vertices - (never|broken|missing|always). - -k, --keep-attribute arg Used repeatedly to build a limiting set of - vertex attributes to keep. - -v, --verbose Enable verbose output. - -h, --help Show this help. - -V, --version Display the current program version. +Positionals: + FBX Model FILE The FBX model to convert. + +Options: + -h,--help Print this help message and exit + -v,--verbose Include blend shape tangents, if reported present by the FBX SDK. + -V,--version + -i,--input FILE The FBX model to convert. + -o,--output TEXT Where to generate the output, without suffix. + -e,--embed Inline buffers as data:// URIs within generated non-binary glTF. + -b,--binary Output a single binary format .glb file. + --long-indices (never|auto|always) + Whether to use 32-bit indices. + --compute-normals (never|broken|missing|always) + When to compute vertex normals from mesh geometry. + --flip-u Flip all U texture coordinates. + --no-flip-u Don't flip U texture coordinates. + --flip-v Flip all V texture coordinates. + --no-flip-v Don't flip V texture coordinates. + --no-khr-lights-punctual Don't use KHR_lights_punctual extension to export FBX lights. + --user-properties Transcribe FBX User Properties into glTF node and material 'extras'. + --blend-shape-normals Include blend shape normals, if reported present by the FBX SDK. + --blend-shape-tangents Include blend shape tangents, if reported present by the FBX SDK. + -k,--keep-attribute (position|normal|tangent|binormial|color|uv0|uv1|auto) ... + Used repeatedly to build a limiting set of vertex attributes to keep. + + +Materials: + --pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes from the FBX. + --khr-materials-unlit Use KHR_materials_unlit extension to request an unlit shader. + + +Draco: + -d,--draco Apply Draco mesh compression to geometries. + --draco-compression-level INT in [0 - 10]=7 + The compression level to tune Draco to. + --draco-bits-for-position INT in [1 - 32]=14 + How many bits to quantize position to. + --draco-bits-for-uv INT in [1 - 32]=10 + How many bits to quantize UV coordinates to. + --draco-bits-for-normals INT in [1 - 32]=10 + How many bits to quantize nornals to. + --draco-bits-for-colors INT in [1 - 32]=8 + How many bits to quantize colors to. + --draco-bits-for-other INT in [1 - 32]=8 + How many bits to quantize all other vertex attributes to. ``` Some of these switches are not obvious: - `--embed` is the way to get a single distributable file without using the - binary format. It encodes the binary buffer(s) as a single enormous - base64-encoded `data://` URI. This is a very slow and space-consuming way to - accomplish what the binary format was invented to do simply and efficiently, - but it can be useful e.g. for loaders that don't understand the .glb format. + binary format. It encodes the binary buffer(s) as a single base64-encoded + `data://` URI. This is a very slow and space-consuming way to accomplish what + the binary format was invented to do simply and efficiently, but it can be + useful e.g. for loaders that don't understand the .glb format. - `--flip-u` and `--flip-v`, when enabled, will apply a `x -> (1.0 - x)` function to all `u` or `v` texture coordinates respectively. The `u` version is perhaps not commonly used, but flipping `v` is **the default behaviour**. @@ -104,49 +121,46 @@ Some of these switches are not obvious: ## Building it on your own -This build process has been tested on Linux, Mac OS X and Windows. It requires -CMake 3.5+ and a reasonably C++11 compliant toolchain. +Building FBX2glTF has become slightly more ornery because explanation. We currently depend on the open source projects [Draco](https://github.com/google/draco), [MathFu](https://github.com/google/mathfu), [Json](https://github.com/nlohmann/json), [cppcodec](https://github.com/tplgy/cppcodec), -[cxxopts](https://github.com/jarro2783/cxxopts), +[CLI11](https://github.com/CLIUtils/CLI11), +[stb](https://github.com/nothings/stb), and [fmt](https://github.com/fmtlib/fmt); -all of which are automatically downloaded, configured and built. +all of which are automatically downloaded and/or built. -You must manually download and install the +You must however manually download and install the [Autodesk FBX SDK](https://www.autodesk.com/products/fbx/overview) and accept its license agreement. -**At present, only version 2018.1.1 of the FBX SDK is supported**. The +**At present, only version 2019.2 of the FBX SDK is supported**. The build system will not successfully locate any other version. ### Linux and MacOS X -Compilation on Unix machines should be as simple as: +Compilation on Unix machines might look like: ``` - > cd - > cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release - > make -Cbuild -j4 install + ``` If all goes well, you will end up with a statically linked executable. ### Windows + this needs updating + Windows users may [download](https://cmake.org/download) CMake for Windows, install it and [run it](https://cmake.org/runningcmake/) on the FBX2glTF checkout (choose a build directory distinct from the source). As part of this process, you will be asked to choose which generator -to use. **At present, only Visual Studio 2017 is supported.** Older +to use. **At present, only Visual Studio 2017 or 2019 is supported.** Older versions of the IDE are unlikely to successfully build the tool. -*(MinGW support is plausible. The difficulty is linking statically against the -FBX SDK .lib file. Contributions welcome.)* - Note that the `CMAKE_BUILD_TYPE` variable from the Unix Makefile system is entirely ignored here; it is when you open the generated solution that you will be choose one of the canonical build types — *Debug*, diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..d997220 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,18 @@ +import os + +from conans import ConanFile, CMake + +class FBX2glTFConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + requires = (("boost_filesystem/1.69.0@bincrafters/stable"), + ("fmt/5.3.0@bincrafters/stable")) + generators = "cmake_find_package", "cmake_paths" + + def build(self): + if os.environ.get('FBXSDK_SDKS') == None: + print("Please set the environment variable FBXSDK_SDKS.") + return + cmake = CMake(self) + cmake.definitions["FBXSDK_SDKS"] = os.getenv('FBXSDK_SDKS') + cmake.configure() + cmake.build() diff --git a/src/FBX2glTF.cpp b/src/FBX2glTF.cpp index 270fcd7..2171c0a 100644 --- a/src/FBX2glTF.cpp +++ b/src/FBX2glTF.cpp @@ -13,13 +13,6 @@ #include #include -#if defined(__unix__) || defined(__APPLE__) - -#include - -#define _stricmp strcasecmp -#endif - #include #include "FBX2glTF.h" @@ -271,10 +264,7 @@ int main(int argc, char* argv[]) { if (outputPath.empty()) { // if -o is not given, default to the basename of the .fbx - outputPath = fmt::format( - ".{}{}", - (const char)StringUtils::GetPathSeparator(), - StringUtils::GetFileBaseString(inputPath)); + outputPath = "./" + FileUtils::GetFileBase(inputPath); } // the output folder in .gltf mode, not used for .glb std::string outputFolder; @@ -282,14 +272,17 @@ int main(int argc, char* argv[]) { // the path of the actual .glb or .gltf file std::string modelPath; if (gltfOptions.outputBinary) { - // in binary mode, we write precisely where we're asked - modelPath = outputPath + ".glb"; - + const auto& suffix = FileUtils::GetFileSuffix(outputPath); + // add .glb to output path, unless it already ends in exactly that + if (suffix.has_value() && suffix.value() == "glb") { + modelPath = outputPath; + } else { + modelPath = outputPath + ".glb"; + } } else { // in gltf mode, we create a folder and write into that - outputFolder = - fmt::format("{}_out{}", outputPath.c_str(), (const char)StringUtils::GetPathSeparator()); - modelPath = outputFolder + StringUtils::GetFileNameString(outputPath) + ".gltf"; + outputFolder = fmt::format("{}_out/", outputPath.c_str()); + modelPath = outputFolder + FileUtils::GetFileName(outputPath) + ".gltf"; } if (!FileUtils::CreatePath(modelPath.c_str())) { fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str()); @@ -302,7 +295,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, {"png", "jpg", "jpeg"})) { 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..5a0bb0a 100644 --- a/src/FBX2glTF.h +++ b/src/FBX2glTF.h @@ -9,9 +9,10 @@ #pragma once +#include #include -#if defined ( _WIN32 ) +#if defined(_WIN32) // Tell Windows not to define min() and max() macros #define NOMINMAX #include @@ -20,20 +21,22 @@ #define FBX2GLTF_VERSION std::string("0.9.6") #include + #include -#if defined ( _WIN32 ) +#if defined(_WIN32) // this is defined in fbxmath.h #undef isnan +#undef snprintf #endif #include "mathfu.hpp" // give all modules access to our tweaked JSON -#include #include +#include -template +template using workaround_fifo_map = nlohmann::fifo_map, A>; using json = nlohmann::basic_json; @@ -41,66 +44,75 @@ using json = nlohmann::basic_json; extern bool verboseOutput; /** -* The variuos situations in which the user may wish for us to (re-)compute normals for our vertices. -*/ + * Centralises all the laborious downcasting from your OS' 64-bit + * index variables down to the uint32s that glTF is built out of. + */ +inline uint32_t to_uint32(size_t n) { + assert(n < UINT_MAX); + return static_cast(n); +} + +/** + * The variuos situations in which the user may wish for us to (re-)compute normals for our + * vertices. + */ enum class ComputeNormalsOption { - NEVER, // do not ever compute any normals (results in broken glTF for some sources) - BROKEN, // replace zero-length normals in any mesh that has a normal layer - MISSING, // if a mesh lacks normals, compute them all - ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before + NEVER, // do not ever compute any normals (results in broken glTF for some sources) + BROKEN, // replace zero-length normals in any mesh that has a normal layer + MISSING, // if a mesh lacks normals, compute them all + ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before }; enum class UseLongIndicesOptions { - NEVER, // only ever use 16-bit indices - AUTO, // use shorts or longs depending on vertex count - ALWAYS, // only ever use 32-bit indices + NEVER, // only ever use 16-bit indices + AUTO, // use shorts or longs depending on vertex count + ALWAYS, // only ever use 32-bit indices }; /** -* User-supplied options that dictate the nature of the glTF being generated. -*/ -struct GltfOptions -{ - /** - * If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that - * specify the largest set of attributes that'll ever be kept for a vertex. - * The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the - * attributes to keep are inferred from which textures are supplied. - */ - int keepAttribs { -1 }; - /** Whether to output a .glb file, the binary format of glTF. */ - bool outputBinary { false }; - /** If non-binary, whether to inline all resources, for a single (large) .glTF file. */ - bool embedResources { false }; + * User-supplied options that dictate the nature of the glTF being generated. + */ +struct GltfOptions { + /** + * If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that + * specify the largest set of attributes that'll ever be kept for a vertex. + * The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the + * attributes to keep are inferred from which textures are supplied. + */ + int keepAttribs{-1}; + /** Whether to output a .glb file, the binary format of glTF. */ + bool outputBinary{false}; + /** If non-binary, whether to inline all resources, for a single (large) .glTF file. */ + bool embedResources{false}; - /** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */ - struct { - bool enabled = false; - int compressionLevel = 7; - int quantBitsPosition = 14; - int quantBitsTexCoord = 10; - int quantBitsNormal = 10; - int quantBitsColor = 8; - int quantBitsGeneric = 8; - } draco; + /** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */ + struct { + bool enabled = false; + int compressionLevel = 7; + int quantBitsPosition = 14; + int quantBitsTexCoord = 10; + int quantBitsNormal = 10; + int quantBitsColor = 8; + int quantBitsGeneric = 8; + } draco; - /** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */ - bool enableUserProperties { false }; + /** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */ + bool enableUserProperties{false}; - /** Whether to use KHR_materials_unlit to extend materials definitions. */ - bool useKHRMatUnlit { false }; - /** Whether to populate the pbrMetallicRoughness substruct in materials. */ - bool usePBRMetRough { false }; + /** Whether to use KHR_materials_unlit to extend materials definitions. */ + bool useKHRMatUnlit{false}; + /** Whether to populate the pbrMetallicRoughness substruct in materials. */ + bool usePBRMetRough{false}; - /** Whether to include lights through the KHR_punctual_lights extension. */ - bool useKHRLightsPunctual { true }; + /** Whether to include lights through the KHR_punctual_lights extension. */ + bool useKHRLightsPunctual{true}; - /** 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. */ - bool useBlendShapeTangents { false }; - /** When to compute vertex normals from geometry. */ - ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; - /** When to use 32-bit indices. */ - UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; + /** 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. */ + bool useBlendShapeTangents{false}; + /** When to compute vertex normals from geometry. */ + ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; + /** When to use 32-bit indices. */ + UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; }; diff --git a/src/fbx/Fbx2Raw.cpp b/src/fbx/Fbx2Raw.cpp index 9a11bdd..0c28498 100644 --- a/src/fbx/Fbx2Raw.cpp +++ b/src/fbx/Fbx2Raw.cpp @@ -916,39 +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. - const std::string fileName = - StringUtils::GetFileNameString(StringUtils::GetCleanPathString(fbxFileName)); + + // 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 = StringUtils::GetFileBaseString(fileName); + 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, StringUtils::GetFileBaseString(file)) == 0) { - // Return the name with extension of the file in the directory. - return std::string(directory) + file; + if (StringUtils::CompareNoCase(fileBase, FileUtils::GetFileBase(file)) == 0) { + 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 @@ -960,50 +982,59 @@ static std::string GetInferredFileName( */ static void FindFbxTextures( FbxScene* pScene, - const char* fbxFileName, - const char* extensions, + const std::string& fbxFileName, + const std::set& extensions, std::map& textureLocations) { - // Get the folder the FBX file is in. - const std::string folder = StringUtils::GetFolderString(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 + StringUtils::GetFileBaseString(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()); } } -bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions) { +bool LoadFBXFile( + RawModel& raw, + const std::string fbxFileName, + const std::set& textureExtensions) { FbxManager* pManager = FbxManager::Create(); FbxIOSettings* pIoSettings = FbxIOSettings::Create(pManager, IOSROOT); pManager->SetIOSettings(pIoSettings); FbxImporter* pImporter = FbxImporter::Create(pManager, ""); - if (!pImporter->Initialize(fbxFileName, -1, pManager->GetIOSettings())) { + if (!pImporter->Initialize(fbxFileName.c_str(), -1, pManager->GetIOSettings())) { if (verboseOutput) { fmt::printf("%s\n", pImporter->GetStatus().GetErrorString()); } diff --git a/src/fbx/Fbx2Raw.hpp b/src/fbx/Fbx2Raw.hpp index a5fd231..7933af1 100644 --- a/src/fbx/Fbx2Raw.hpp +++ b/src/fbx/Fbx2Raw.hpp @@ -11,6 +11,9 @@ #include "raw/RawModel.hpp" -bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions); +bool LoadFBXFile( + RawModel& raw, + const std::string fbxFileName, + const std::set& textureExtensions); json TranscribeProperty(FbxProperty& prop); \ No newline at end of file diff --git a/src/fbx/FbxBlendShapesAccess.hpp b/src/fbx/FbxBlendShapesAccess.hpp index fc99ec9..85fd32d 100644 --- a/src/fbx/FbxBlendShapesAccess.hpp +++ b/src/fbx/FbxBlendShapesAccess.hpp @@ -95,7 +95,7 @@ class FbxBlendShapesAccess { } FbxAnimCurve* GetAnimation(size_t channelIx, size_t animIx) const { - return channels.at(channelIx).ExtractAnimation(animIx); + return channels.at(channelIx).ExtractAnimation(to_uint32(animIx)); } private: diff --git a/src/fbx/FbxSkinningAccess.hpp b/src/fbx/FbxSkinningAccess.hpp index d2b42d8..d36c04f 100644 --- a/src/fbx/FbxSkinningAccess.hpp +++ b/src/fbx/FbxSkinningAccess.hpp @@ -37,7 +37,7 @@ class FbxSkinningAccess { return jointNodes[jointIndex]; } - const long GetJointId(const int jointIndex) const { + const uint64_t GetJointId(const int jointIndex) const { return jointIds[jointIndex]; } @@ -49,7 +49,7 @@ class FbxSkinningAccess { return jointInverseGlobalTransforms[jointIndex]; } - const long GetRootNode() const { + const uint64_t GetRootNode() const { assert(rootIndex != -1); return jointIds[rootIndex]; } @@ -70,7 +70,7 @@ class FbxSkinningAccess { private: int rootIndex; - std::vector jointIds; + std::vector jointIds; std::vector jointNodes; std::vector jointSkinningTransforms; std::vector jointInverseGlobalTransforms; diff --git a/src/gltf/GltfModel.cpp b/src/gltf/GltfModel.cpp index c3c6524..7bf53ba 100644 --- a/src/gltf/GltfModel.cpp +++ b/src/gltf/GltfModel.cpp @@ -12,7 +12,7 @@ std::shared_ptr GltfModel::GetAlignedBufferView( BufferData& buffer, const BufferViewData::GL_ArrayType target) { - unsigned long bufferSize = this->binary->size(); + uint32_t bufferSize = to_uint32(this->binary->size()); if ((bufferSize % 4) > 0) { bufferSize += (4 - (bufferSize % 4)); this->binary->resize(bufferSize); @@ -27,7 +27,7 @@ GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t byt bufferView->byteLength = bytes; // make space for the new bytes (possibly moving the underlying data) - unsigned long bufferSize = this->binary->size(); + uint32_t bufferSize = to_uint32(this->binary->size()); this->binary->resize(bufferSize + bytes); // and copy them into place @@ -52,7 +52,7 @@ std::shared_ptr GltfModel::AddBufferViewForFile( std::vector fileBuffer(size); if (file.read(fileBuffer.data(), size)) { - result = AddRawBufferView(buffer, fileBuffer.data(), size); + result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size)); } else { fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename); } diff --git a/src/gltf/GltfModel.hpp b/src/gltf/GltfModel.hpp index f82b09f..7c7d52c 100644 --- a/src/gltf/GltfModel.hpp +++ b/src/gltf/GltfModel.hpp @@ -44,7 +44,7 @@ template class Holder { public: std::shared_ptr hold(T* ptr) { - ptr->ix = ptrs.size(); + ptr->ix = to_uint32(ptrs.size()); ptrs.emplace_back(ptr); return ptrs.back(); } @@ -114,7 +114,7 @@ class GltfModel { primitive.AddDracoAttrib(attrDef, attribArr); accessor = accessors.hold(new AccessorData(attrDef.glType)); - accessor->count = attribArr.size(); + accessor->count = to_uint32(attribArr.size()); } else { auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER); accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string("")); diff --git a/src/gltf/Raw2Gltf.cpp b/src/gltf/Raw2Gltf.cpp index 84ec82b..e081311 100644 --- a/src/gltf/Raw2Gltf.cpp +++ b/src/gltf/Raw2Gltf.cpp @@ -256,29 +256,28 @@ ModelData* Raw2Gltf( if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) { /** * PBR FBX Material -> PBR Met/Rough glTF. + * + * METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture. + * Other values translate directly. */ RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get(); - // diffuse and emissive are noncontroversial - baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); - diffuseFactor = props->diffuseFactor; - emissiveFactor = props->emissiveFactor; - emissiveIntensity = props->emissiveIntensity; - - // we always send the metallic/roughness factors onto the glTF generator - metallic = props->metallic; - roughness = props->roughness; - // determine if we need to generate a combined map bool hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0; bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0; bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0; bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap) : (hasRoughnessMap && hasMetallicMap); - if (atLeastTwoMaps) { - // if there's at least two of metallic/roughness/occlusion, it makes sense to - // merge them: occlusion into the red channel, metallic into blue channel, and - // roughness into the green. + if (!atLeastTwoMaps) { + // this handles the case of 0 or 1 maps supplied + aoMetRoughTex = hasMetallicMap + ? simpleTex(RAW_TEXTURE_USAGE_METALLIC) + : (hasRoughnessMap + ? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS) + : (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr)); + } else { + // otherwise merge occlusion into the red channel, metallic into blue channel, and + // roughness into the green, of a new combinatory texture aoMetRoughTex = textureBuilder.combine( { material.textures[RAW_TEXTURE_USAGE_OCCLUSION], @@ -289,27 +288,24 @@ ModelData* Raw2Gltf( [&](const std::vector pixels) -> TextureBuilder::pixel { const float occlusion = (*pixels[0])[0]; - const float metallic = (*pixels[1])[0]; - const float roughness = (*pixels[2])[0]; + const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic); + const float roughness = + (*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness); return {{occlusion, props->invertRoughnessMap ? 1.0f - roughness : roughness, metallic, 1}}; }, false); - if (hasOcclusionMap) { - // will only be true if there were actual non-trivial pixels - occlusionTexture = aoMetRoughTex.get(); - } - } else { - // this handles the case of 0 or 1 maps supplied - if (hasMetallicMap) { - aoMetRoughTex = simpleTex(RAW_TEXTURE_USAGE_METALLIC); - } else if (hasRoughnessMap) { - aoMetRoughTex = simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS); - } - // else only occlusion map is possible: that check is handled further below } + baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO); + diffuseFactor = props->diffuseFactor; + metallic = props->metallic; + roughness = props->roughness; + emissiveFactor = props->emissiveFactor; + emissiveIntensity = props->emissiveIntensity; + // this will set occlusionTexture to null, if no actual occlusion map exists + occlusionTexture = aoMetRoughTex.get(); } else { /** * Traditional FBX Material -> PBR Met/Rough glTF. @@ -393,7 +389,6 @@ ModelData* Raw2Gltf( khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial()); } - // after all the special cases have had a go, check if we need to look up occlusion map if (!occlusionTexture) { occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get(); } @@ -447,11 +442,11 @@ ModelData* Raw2Gltf( std::shared_ptr primitive; if (options.draco.enabled) { - int triangleCount = surfaceModel.GetTriangleCount(); + size_t triangleCount = surfaceModel.GetTriangleCount(); // initialize Draco mesh with vertex index information auto dracoMesh(std::make_shared()); - dracoMesh->SetNumFaces(static_cast(triangleCount)); + dracoMesh->SetNumFaces(triangleCount); dracoMesh->set_num_points(surfaceModel.GetVertexCount()); for (uint32_t ii = 0; ii < triangleCount; ii++) { @@ -464,7 +459,7 @@ ModelData* Raw2Gltf( AccessorData& indexes = *gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT)); - indexes.count = 3 * triangleCount; + indexes.count = to_uint32(3 * triangleCount); primitive.reset(new PrimitiveData(indexes, mData, dracoMesh)); } else { const AccessorData& indexes = *gltf->AddAccessorWithView( @@ -499,11 +494,13 @@ ModelData* Raw2Gltf( GLT_VEC3F, draco::GeometryAttribute::NORMAL, draco::DT_FLOAT32); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_NORMAL); + const auto _ = + gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_NORMAL); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) { const AttributeDefinition ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_TANGENT); + const auto _ = gltf->AddAttributeToPrimitive( + buffer, surfaceModel, *primitive, ATTR_TANGENT); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) { const AttributeDefinition ATTR_COLOR( @@ -512,7 +509,8 @@ ModelData* Raw2Gltf( GLT_VEC4F, draco::GeometryAttribute::COLOR, draco::DT_FLOAT32); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_COLOR); + const auto _ = + gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_COLOR); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) { const AttributeDefinition ATTR_TEXCOORD_0( @@ -521,7 +519,8 @@ ModelData* Raw2Gltf( GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0); + const auto _ = gltf->AddAttributeToPrimitive( + buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) { const AttributeDefinition ATTR_TEXCOORD_1( @@ -530,7 +529,8 @@ ModelData* Raw2Gltf( GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1); + const auto _ = gltf->AddAttributeToPrimitive( + buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) { const AttributeDefinition ATTR_JOINTS( @@ -539,7 +539,8 @@ ModelData* Raw2Gltf( GLT_VEC4I, draco::GeometryAttribute::GENERIC, draco::DT_UINT16); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_JOINTS); + const auto _ = + gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_JOINTS); } if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) { const AttributeDefinition ATTR_WEIGHTS( @@ -548,7 +549,8 @@ ModelData* Raw2Gltf( GLT_VEC4F, draco::GeometryAttribute::GENERIC, draco::DT_FLOAT32); - gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_WEIGHTS); + const auto _ = + gltf->AddAttributeToPrimitive(buffer, surfaceModel, *primitive, ATTR_WEIGHTS); } // each channel present in the mesh always ends up a target in the primitive @@ -633,7 +635,7 @@ ModelData* Raw2Gltf( draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer); assert(status.code() == draco::Status::OK); - auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size()); + auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size())); primitive->NoteDracoBuffer(*view); } mesh->AddPrimitive(primitive); @@ -735,7 +737,7 @@ ModelData* Raw2Gltf( type = LightData::Type::Spot; break; } - gltf->lights.hold(new LightData( + const auto _ = gltf->lights.hold(new LightData( light.name, type, light.color, @@ -842,13 +844,13 @@ ModelData* Raw2Gltf( gltfOutStream.write(glb2BinaryHeader, 8); // append binary buffer directly to .glb file - uint32_t binaryLength = gltf->binary->size(); + size_t binaryLength = gltf->binary->size(); gltfOutStream.write((const char*)&(*gltf->binary)[0], binaryLength); while ((binaryLength % 4) != 0) { gltfOutStream.put('\0'); binaryLength++; } - uint32_t totalLength = (uint32_t)gltfOutStream.tellp(); + uint32_t totalLength = to_uint32(gltfOutStream.tellp()); // seek back to sub-header for json chunk gltfOutStream.seekp(8); diff --git a/src/gltf/Raw2Gltf.hpp b/src/gltf/Raw2Gltf.hpp index 0dbdc6e..cd44b41 100644 --- a/src/gltf/Raw2Gltf.hpp +++ b/src/gltf/Raw2Gltf.hpp @@ -120,7 +120,7 @@ const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"}; * The base of any indexed glTF entity. */ struct Holdable { - uint32_t ix; + uint32_t ix = UINT_MAX; virtual json serialize() const = 0; }; diff --git a/src/gltf/TextureBuilder.cpp b/src/gltf/TextureBuilder.cpp index 4329c5f..c09393c 100644 --- a/src/gltf/TextureBuilder.cpp +++ b/src/gltf/TextureBuilder.cpp @@ -49,8 +49,7 @@ std::shared_ptr TextureBuilder::combine( if (rawTexIx >= 0) { const RawTexture& rawTex = raw.GetTexture(rawTexIx); const std::string& fileLoc = rawTex.fileLocation; - const std::string& name = - StringUtils::GetFileBaseString(StringUtils::GetFileNameString(fileLoc)); + const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc)); if (!fileLoc.empty()) { info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0); if (!info.pixels) { @@ -142,7 +141,7 @@ std::shared_ptr TextureBuilder::combine( ImageData* image; if (options.outputBinary) { const auto bufferView = - gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), imgBuffer.size()); + gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size())); image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg"); } else { const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg"); @@ -180,20 +179,30 @@ std::shared_ptr TextureBuilder::simple(int rawTexIndex, const std:: } const RawTexture& rawTexture = raw.GetTexture(rawTexIndex); - const std::string textureName = StringUtils::GetFileBaseString(rawTexture.name); - const std::string relativeFilename = StringUtils::GetFileNameString(rawTexture.fileLocation); + const std::string textureName = FileUtils::GetFileBase(rawTexture.name); + const std::string relativeFilename = FileUtils::GetFileName(rawTexture.fileLocation); ImageData* image = nullptr; if (options.outputBinary) { auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation); if (bufferView) { - std::string suffix = StringUtils::GetFileSuffixString(rawTexture.fileLocation); - image = new ImageData(relativeFilename, *bufferView, ImageUtils::suffixToMimeType(suffix)); + const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation); + std::string mimeType; + if (suffix) { + mimeType = ImageUtils::suffixToMimeType(suffix.value()); + } else { + mimeType = "image/jpeg"; + fmt::printf( + "Warning: Can't deduce mime type of texture '%s'; using %s.\n", + rawTexture.fileLocation, + mimeType); + } + image = new ImageData(relativeFilename, *bufferView, mimeType); } } else if (!relativeFilename.empty()) { image = new ImageData(relativeFilename, relativeFilename); - std::string outputPath = outputFolder + StringUtils::NormalizePath(relativeFilename); + std::string outputPath = outputFolder + "/" + relativeFilename; if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) { if (verboseOutput) { fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath); diff --git a/src/gltf/properties/AnimationData.cpp b/src/gltf/properties/AnimationData.cpp index a1dd09b..a2fcced 100644 --- a/src/gltf/properties/AnimationData.cpp +++ b/src/gltf/properties/AnimationData.cpp @@ -24,7 +24,7 @@ void AnimationData::AddNodeChannel( const AccessorData& accessor, std::string path) { assert(channels.size() == samplers.size()); - uint32_t ix = channels.size(); + uint32_t ix = to_uint32(channels.size()); channels.emplace_back(channel_t(ix, node, std::move(path))); samplers.emplace_back(sampler_t(timeAccessor, accessor.ix)); } diff --git a/src/gltf/properties/MaterialData.cpp b/src/gltf/properties/MaterialData.cpp index 8ac6bfa..c1697b3 100644 --- a/src/gltf/properties/MaterialData.cpp +++ b/src/gltf/properties/MaterialData.cpp @@ -59,13 +59,15 @@ void to_json(json& j, const PBRMetallicRoughness& d) { if (d.baseColorFactor.LengthSquared() > 0) { j["baseColorFactor"] = toStdVec(d.baseColorFactor); } - // we always copy metallic/roughness straight to the glTF: - // - if there's a texture, they're linear multiplier - // - if there's no texture, they're constants - j["metallicFactor"] = d.metallic; - j["roughnessFactor"] = d.roughness; if (d.metRoughTexture != nullptr) { j["metallicRoughnessTexture"] = *d.metRoughTexture; + // if a texture is provided, throw away metallic/roughness values + j["roughnessFactor"] = 1.0f; + j["metallicFactor"] = 1.0f; + } else { + // without a texture, however, use metallic/roughness as constants + j["metallicFactor"] = d.metallic; + j["roughnessFactor"] = d.roughness; } } diff --git a/src/gltf/properties/PrimitiveData.hpp b/src/gltf/properties/PrimitiveData.hpp index 16bf7e9..3153229 100644 --- a/src/gltf/properties/PrimitiveData.hpp +++ b/src/gltf/properties/PrimitiveData.hpp @@ -49,7 +49,7 @@ struct PrimitiveData { componentCount * draco::DataTypeLength(attribute.dracoComponentType), 0); - const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size()); + const int dracoAttId = dracoMesh->AddAttribute(att, true, to_uint32(attribArr.size())); draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); std::vector buf(sizeof(T)); diff --git a/src/mathfu.hpp b/src/mathfu.hpp index aaed7dd..2dd8301 100644 --- a/src/mathfu.hpp +++ b/src/mathfu.hpp @@ -9,6 +9,8 @@ #pragma once +#include + #include #include diff --git a/src/utils/File_Utils.cpp b/src/utils/File_Utils.cpp index 4c044fe..4f9885d 100644 --- a/src/utils/File_Utils.cpp +++ b/src/utils/File_Utils.cpp @@ -10,171 +10,47 @@ #include "File_Utils.hpp" #include +#include #include #include #include #include -#if defined(__unix__) || defined(__APPLE__) - -#include -#include -#include -#include -#include - -#define _getcwd getcwd -#define _mkdir(a) mkdir(a, 0777) -#elif defined(_WIN32) -#include -#include -#else -#include -#include -#endif - -#include - #include "FBX2glTF.h" #include "String_Utils.hpp" namespace FileUtils { -std::string GetCurrentFolder() { - char cwd[StringUtils::MAX_PATH_LENGTH]; - if (!_getcwd(cwd, sizeof(cwd))) { - return std::string(); - } - cwd[sizeof(cwd) - 1] = '\0'; - StringUtils::GetCleanPath(cwd, cwd, StringUtils::PATH_UNIX); - const size_t length = strlen(cwd); - if (cwd[length - 1] != '/' && length < StringUtils::MAX_PATH_LENGTH - 1) { - cwd[length + 0] = '/'; - cwd[length + 1] = '\0'; - } - return std::string(cwd); -} - -bool FileExists(const std::string& filePath) { - std::ifstream stream(filePath); - return stream.good(); -} - -bool FolderExists(const std::string& folderPath) { -#if defined(__unix__) || defined(__APPLE__) - DIR* dir = opendir(folderPath.c_str()); - if (dir) { - closedir(dir); - return true; - } - return false; -#else - const DWORD ftyp = GetFileAttributesA(folderPath.c_str()); - if (ftyp == INVALID_FILE_ATTRIBUTES) { - return false; // bad path - } - return (ftyp & FILE_ATTRIBUTE_DIRECTORY) != 0; -#endif -} - -bool MatchExtension(const char* fileExtension, const char* matchExtensions) { - if (matchExtensions[0] == '\0') { - return true; - } - if (fileExtension[0] == '.') { - fileExtension++; - } - for (const char* end = matchExtensions; end[0] != '\0';) { - for (; end[0] == ';'; end++) { - } - const char* ext = end; - for (; end[0] != ';' && end[0] != '\0'; end++) { - } -#if defined(__unix__) || defined(__APPLE__) - if (strncasecmp(fileExtension, ext, end - ext) == 0) -#else - if (_strnicmp(fileExtension, ext, end - ext) == 0) -#endif - { - return true; - } - } - return false; -} - -std::vector ListFolderFiles(const char* folder, const char* matchExtensions) { +std::vector ListFolderFiles( + std::string folder, + const std::set& matchExtensions) { std::vector fileList; -#if defined(__unix__) || defined(__APPLE__) - DIR* dir = opendir(strlen(folder) > 0 ? folder : "."); - if (dir != nullptr) { - for (;;) { - struct dirent* dp = readdir(dir); - if (dp == nullptr) { - break; + if (folder.empty()) { + folder = "."; + } + for (const auto& entry : boost::filesystem::directory_iterator(folder)) { + const auto& suffix = FileUtils::GetFileSuffix(entry.path().string()); + 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()); } - - if (dp->d_type == DT_DIR) { - continue; - } - - const char* fileName = dp->d_name; - const char* fileExt = strrchr(fileName, '.'); - - if (!fileExt || !MatchExtension(fileExt, matchExtensions)) { - continue; - } - - fileList.emplace_back(fileName); } - - closedir(dir); } -#else - std::string pathStr = folder; - pathStr += "*"; - - WIN32_FIND_DATA FindFileData; - HANDLE hFind = FindFirstFile(pathStr.c_str(), &FindFileData); - if (hFind != INVALID_HANDLE_VALUE) { - do { - if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { - std::string fileName = FindFileData.cFileName; - std::string::size_type extPos = fileName.rfind('.'); - if (extPos != std::string::npos && - MatchExtension(fileName.substr(extPos + 1).c_str(), matchExtensions)) { - fileList.push_back(fileName); - } - } - } while (FindNextFile(hFind, &FindFileData)); - - FindClose(hFind); - } -#endif return fileList; } -bool CreatePath(const char* path) { -#if defined(__unix__) || defined(__APPLE__) - StringUtils::PathSeparator separator = StringUtils::PATH_UNIX; -#else - StringUtils::PathSeparator separator = StringUtils::PATH_WIN; -#endif - std::string folder = StringUtils::GetFolderString(path); - std::string clean = StringUtils::GetCleanPathString(folder, separator); - std::string build = clean; - for (int i = 0; i < clean.length(); i++) { - if (clean[i] == separator && i > 0) { - build[i] = '\0'; - if (i > 1 || build[1] != ':') { - if (_mkdir(build.c_str()) != 0 && errno != EEXIST) { - return false; - } - } - } - build[i] = clean[i]; +bool CreatePath(const std::string path) { + const auto& parent = boost::filesystem::path(path).parent_path(); + if (parent.empty()) { + // this is either CWD or boost::filesystem root; either way it exists + return true; } - return true; + if (boost::filesystem::exists(parent)) { + return boost::filesystem::is_directory(parent); + } + return boost::filesystem::create_directory(parent); } bool CopyFile(const std::string& srcFilename, const std::string& dstFilename, bool createPath) { diff --git a/src/utils/File_Utils.hpp b/src/utils/File_Utils.hpp index 0f9a702..92827de 100644 --- a/src/utils/File_Utils.hpp +++ b/src/utils/File_Utils.hpp @@ -9,9 +9,13 @@ #pragma once +#include #include #include +#include +#include + namespace FileUtils { std::string GetCurrentFolder(); @@ -19,13 +23,51 @@ std::string GetCurrentFolder(); bool FileExists(const std::string& folderPath); bool FolderExists(const std::string& folderPath); -bool MatchExtension(const char* fileExtension, const char* matchExtensions); -std::vector ListFolderFiles(const char* folder, const char* matchExtensions); +std::vector ListFolderFiles( + const std::string folder, + const std::set& matchExtensions); -bool CreatePath(const char* path); +bool CreatePath(std::string path); bool CopyFile( const std::string& srcFilename, const std::string& dstFilename, bool createPath = false); + +inline std::string GetAbsolutePath(const std::string& filePath) { + return boost::filesystem::absolute(filePath).string(); +} + +inline std::string GetCurrentFolder() { + return boost::filesystem::current_path().string(); +} + +inline bool FileExists(const std::string& filePath) { + return boost::filesystem::exists(filePath) && boost::filesystem::is_regular_file(filePath); +} + +inline bool FolderExists(const std::string& folderPath) { + return boost::filesystem::exists(folderPath) && boost::filesystem::is_directory(folderPath); +} + +inline std::string getFolder(const std::string& path) { + return boost::filesystem::path(path).parent_path().string(); +} + +inline std::string GetFileName(const std::string& path) { + return boost::filesystem::path(path).filename().string(); +} + +inline std::string GetFileBase(const std::string& path) { + return boost::filesystem::path(path).stem().string(); +} + +inline boost::optional GetFileSuffix(const std::string& path) { + const auto& extension = boost::filesystem::path(path).extension(); + if (extension.empty()) { + return boost::none; + } + return extension.string().substr(1); +} + } // namespace FileUtils diff --git a/src/utils/String_Utils.cpp b/src/utils/String_Utils.cpp deleted file mode 100644 index 8cf8063..0000000 --- a/src/utils/String_Utils.cpp +++ /dev/null @@ -1,80 +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" - -namespace StringUtils { - -PathSeparator operator!(const PathSeparator& s) { - return (s == PATH_WIN) ? PATH_UNIX : PATH_WIN; -} - -PathSeparator GetPathSeparator() { -#if defined(__unix__) || defined(__APPLE__) - return PATH_UNIX; -#else - return PATH_WIN; -#endif -} -const std::string NormalizePath(const std::string& path) { - PathSeparator separator = GetPathSeparator(); - char replace; - if (separator == PATH_WIN) { - replace = PATH_UNIX; - } else { - replace = PATH_WIN; - } - std::string normalizedPath = path; - for (size_t s = normalizedPath.find(replace, 0); s != std::string::npos; - s = normalizedPath.find(replace, s)) { - normalizedPath[s] = separator; - } - return normalizedPath; -} - -const std::string GetFolderString(const std::string& path) { - size_t s = path.rfind(PATH_WIN); - s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX); - return path.substr(0, s + 1); -} - -const std::string GetCleanPathString(const std::string& path, const PathSeparator separator) { - std::string cleanPath = path; - for (size_t s = cleanPath.find(!separator, 0); s != std::string::npos; - s = cleanPath.find(!separator, s)) { - cleanPath[s] = separator; - } - return cleanPath; -} - -const std::string GetFileNameString(const std::string& path) { - size_t s = path.rfind(PATH_WIN); - s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX); - return path.substr(s + 1, std::string::npos); -} - -const std::string GetFileBaseString(const std::string& path) { - const std::string fileName = GetFileNameString(path); - return fileName.substr(0, fileName.rfind('.')).c_str(); -} - -const std::string GetFileSuffixString(const std::string& path) { - const std::string fileName = GetFileNameString(path); - size_t pos = fileName.rfind('.'); - if (pos == std::string::npos) { - return ""; - } - return fileName.substr(++pos); -} - -int CompareNoCase(const std::string& s1, const std::string& s2) { - return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH); -} - -} // namespace StringUtils diff --git a/src/utils/String_Utils.hpp b/src/utils/String_Utils.hpp index f1a3a65..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,39 +18,17 @@ #if defined(_MSC_VER) #define strncasecmp _strnicmp -#define strcasecmp _stricmp #endif namespace StringUtils { -static const unsigned int MAX_PATH_LENGTH = 1024; - -enum PathSeparator { PATH_WIN = '\\', PATH_UNIX = '/' }; - -PathSeparator operator!(const PathSeparator& s); - -PathSeparator GetPathSeparator(); -const std::string NormalizePath(const std::string& path); - -const std::string GetCleanPathString( - const std::string& path, - const PathSeparator separator = PATH_WIN); - -template -void GetCleanPath(char (&dest)[size], const char* path, const PathSeparator separator = PATH_WIN) { - size_t len = size - 1; - strncpy(dest, path, len); - char* destPtr = dest; - while ((destPtr = strchr(destPtr, !separator)) != nullptr) { - *destPtr = separator; - } +inline std::string ToLower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](uint8_t c) { return std::tolower(c); }); + return s; } -const std::string GetFolderString(const std::string& path); -const std::string GetFileNameString(const std::string& path); -const std::string GetFileBaseString(const std::string& path); -const std::string GetFileSuffixString(const std::string& path); - -int CompareNoCase(const std::string& s1, const std::string& s2); +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())); +} } // namespace StringUtils