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:
parent
4bb4bdbac1
commit
7dd8438c78
|
@ -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
114
README.md
|
@ -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*,
|
||||
|
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(""));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <fbxsdk.h>
|
||||
|
||||
#include <mathfu/matrix.h>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue