Embrace Conan, use it to grab boost::filesystem. (#180)

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.
This commit is contained in:
Pär Winzell 2019-04-19 23:54:11 -07:00 committed by GitHub
parent 4bb4bdbac1
commit 7dd8438c78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 424 additions and 523 deletions

View File

@ -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,12 @@ set(CMAKE_CXX_STANDARD 11)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
include(ExternalProject)
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_paths.cmake")
message(FATAL_ERROR
"The Conan package manager must run ('install') first. ${typical_usage_str}")
endif()
include("${CMAKE_BINARY_DIR}/conan_paths.cmake")
# FBX
foreach (FBXSDK_VERSION "2019.2")
find_package(FBX)
@ -32,6 +43,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 +66,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 +124,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=<INSTALL_DIR>
)
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 +188,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 +202,6 @@ add_dependencies(libFBX2glTF
MathFu
FiFoMap
CPPCodec
Fmt
)
if (NOT MSVC)
@ -222,8 +218,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 +242,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}
)

114
README.md
View File

@ -28,49 +28,66 @@ 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...] [<FBX File>]
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.
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.
--anim-framerate (bake24|bake30|bake60)
Select baked animation framerate.
--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).
--anim-framerate arg Select baked animation framerate
(bake24|bake30|bake60).
-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.
--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**.
@ -106,49 +123,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 <TODO> 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 <FBX2glTF directory>
> cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
> make -Cbuild -j4 install
<TODO>
```
If all goes well, you will end up with a statically linked executable.
### Windows
<TODO> 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*,

18
conanfile.py Normal file
View File

@ -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()

View File

@ -13,13 +13,6 @@
#include <unordered_map>
#include <vector>
#if defined(__unix__) || defined(__APPLE__)
#include <sys/stat.h>
#define _stricmp strcasecmp
#endif
#include <CLI11.hpp>
#include "FBX2glTF.h"
@ -291,10 +284,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;
@ -302,14 +292,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
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());
@ -322,7 +315,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", gltfOptions)) {
if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"}, gltfOptions)) {
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
return 1;
}

View File

@ -9,9 +9,10 @@
#pragma once
#include <climits>
#include <string>
#if defined ( _WIN32 )
#if defined(_WIN32)
// Tell Windows not to define min() and max() macros
#define NOMINMAX
#include <Windows.h>
@ -20,20 +21,22 @@
#define FBX2GLTF_VERSION std::string("0.9.6")
#include <fmt/printf.h>
#include <fbxsdk.h>
#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 <json.hpp>
#include <fifo_map.hpp>
#include <json.hpp>
template<class K, class V, class ignore, class A>
template <class K, class V, class ignore, class A>
using workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
using json = nlohmann::basic_json<workaround_fifo_map>;
@ -41,8 +44,18 @@ using json = nlohmann::basic_json<workaround_fifo_map>;
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<uint32_t>(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
@ -63,21 +76,20 @@ enum class AnimationFramerateOptions {
};
/**
* User-supplied options that dictate the nature of the glTF being generated.
*/
struct GltfOptions
{
* 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 };
int keepAttribs{-1};
/** Whether to output a .glb file, the binary format of glTF. */
bool outputBinary { false };
bool outputBinary{false};
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
bool embedResources { false };
bool embedResources{false};
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
struct {
@ -91,20 +103,20 @@ struct GltfOptions
} draco;
/** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */
bool enableUserProperties { false };
bool enableUserProperties{false};
/** Whether to use KHR_materials_unlit to extend materials definitions. */
bool useKHRMatUnlit { false };
bool useKHRMatUnlit{false};
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
bool usePBRMetRough { false };
bool usePBRMetRough{false};
/** Whether to include lights through the KHR_punctual_lights extension. */
bool useKHRLightsPunctual { true };
bool useKHRLightsPunctual{true};
/** Whether to include blend shape normals, if present according to the SDK. */
bool useBlendShapeNormals { false };
bool useBlendShapeNormals{false};
/** Whether to include blend shape tangents, if present according to the SDK. */
bool useBlendShapeTangents { false };
bool useBlendShapeTangents{false};
/** When to compute vertex normals from geometry. */
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
/** When to use 32-bit indices. */

View File

@ -927,39 +927,61 @@ static void ReadAnimations(RawModel& raw, FbxScene* pScene, const GltfOptions& o
}
}
static std::string GetInferredFileName(
static std::string FindFileLoosely(
const std::string& fbxFileName,
const std::string& directory,
const std::vector<std::string>& 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<std::string>& folders,
const std::vector<std::vector<std::string>>& 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
@ -971,46 +993,52 @@ static std::string GetInferredFileName(
*/
static void FindFbxTextures(
FbxScene* pScene,
const char* fbxFileName,
const char* extensions,
const std::string& fbxFileName,
const std::set<std::string>& extensions,
std::map<const FbxTexture*, FbxString>& 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<std::string> 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<std::string> fileList = FileUtils::ListFolderFiles(searchFolder.c_str(), extensions);
// List the contents of each of these folders (if they exist)
std::vector<std::vector<std::string>> 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<FbxFileTexture>(pScene->GetTexture(i));
if (pFileTexture == nullptr) {
continue;
}
const std::string inferredName =
GetInferredFileName(pFileTexture->GetFileName(), searchFolder, fileList);
if (inferredName.empty()) {
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 local image file for texture: %s.\n"
"Original filename: %s\n",
pFileTexture->GetName(),
pFileTexture->GetFileName());
"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);
}
}
// 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,
const std::string fbxFileName,
const std::set<std::string>& textureExtensions,
const GltfOptions& options) {
FbxManager* pManager = FbxManager::Create();
FbxIOSettings* pIoSettings = FbxIOSettings::Create(pManager, IOSROOT);
@ -1018,7 +1046,7 @@ bool LoadFBXFile(
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());
}

View File

@ -11,6 +11,10 @@
#include "raw/RawModel.hpp"
bool LoadFBXFile(RawModel& raw, const char* fbxFileName, const char* textureExtensions, const GltfOptions& options);
bool LoadFBXFile(
RawModel& raw,
const std::string fbxFileName,
const std::set<std::string>& textureExtensions,
const GltfOptions& options);
json TranscribeProperty(FbxProperty& prop);

View File

@ -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:

View File

@ -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<long> jointIds;
std::vector<uint64_t> jointIds;
std::vector<FbxNode*> jointNodes;
std::vector<FbxMatrix> jointSkinningTransforms;
std::vector<FbxMatrix> jointInverseGlobalTransforms;

View File

@ -12,7 +12,7 @@
std::shared_ptr<BufferViewData> 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<BufferViewData> GltfModel::AddBufferViewForFile(
std::vector<char> 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);
}

View File

@ -44,7 +44,7 @@ template <typename T>
class Holder {
public:
std::shared_ptr<T> 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(""));

View File

@ -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<const TextureBuilder::pixel*> 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
}
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 {
// 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
}
} 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<PrimitiveData> 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<draco::Mesh>());
dracoMesh->SetNumFaces(static_cast<size_t>(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);
const auto _ =
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
const auto _ = gltf->AddAttributeToPrimitive<Vec4f>(
buffer, surfaceModel, *primitive, ATTR_TANGENT);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
const AttributeDefinition<Vec4f> ATTR_COLOR(
@ -512,6 +509,7 @@ ModelData* Raw2Gltf(
GLT_VEC4F,
draco::GeometryAttribute::COLOR,
draco::DT_FLOAT32);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
@ -521,7 +519,8 @@ ModelData* Raw2Gltf(
GLT_VEC2F,
draco::GeometryAttribute::TEX_COORD,
draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1(
@ -530,7 +529,8 @@ ModelData* Raw2Gltf(
GLT_VEC2F,
draco::GeometryAttribute::TEX_COORD,
draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
const AttributeDefinition<Vec4i> ATTR_JOINTS(
@ -539,6 +539,7 @@ ModelData* Raw2Gltf(
GLT_VEC4I,
draco::GeometryAttribute::GENERIC,
draco::DT_UINT16);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
@ -548,6 +549,7 @@ ModelData* Raw2Gltf(
GLT_VEC4F,
draco::GeometryAttribute::GENERIC,
draco::DT_FLOAT32);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
}
@ -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);

View File

@ -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;
};

View File

@ -49,8 +49,7 @@ std::shared_ptr<TextureData> 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<TextureData> 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<TextureData> 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);

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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<uint8_t> buf(sizeof(T));

View File

@ -9,6 +9,8 @@
#pragma once
#include <vector>
#include <fbxsdk.h>
#include <mathfu/matrix.h>

View File

@ -10,171 +10,47 @@
#include "File_Utils.hpp"
#include <fstream>
#include <set>
#include <string>
#include <vector>
#include <stdint.h>
#include <stdio.h>
#if defined(__unix__) || defined(__APPLE__)
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define _getcwd getcwd
#define _mkdir(a) mkdir(a, 0777)
#elif defined(_WIN32)
#include <direct.h>
#include <process.h>
#else
#include <direct.h>
#include <process.h>
#endif
#include <sys/stat.h>
#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<std::string> ListFolderFiles(const char* folder, const char* matchExtensions) {
std::vector<std::string> ListFolderFiles(
std::string folder,
const std::set<std::string>& matchExtensions) {
std::vector<std::string> 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 = ".";
}
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);
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());
}
}
} 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;
}
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) {

View File

@ -9,9 +9,13 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
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<std::string> ListFolderFiles(const char* folder, const char* matchExtensions);
std::vector<std::string> ListFolderFiles(
const std::string folder,
const std::set<std::string>& 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<std::string> 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

View File

@ -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

View File

@ -9,6 +9,8 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <cstdarg>
#include <cstdio>
#include <cstring>
@ -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 <size_t size>
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