Switch from CXXOPTS to CLI11. (#148)
We want to move to auto-formatting all our code, and it just seemed impossible to make cxxopts usage tidy under clang-format's dominion. While trying to work out its quirks, I realised that CLI11 did everything I wanted much better, and so we've switched. We're also going to chuck the usage of ExternalProject_Add(), at least for the simplest use cases such as single-header include files. We'll just commit them directly; that's kind of the whole point. The one discipline we'll maintain is that commits that involve third_party/ should be as self-contained as possible (without breaking the app).
This commit is contained in:
parent
5389d848e2
commit
be1b75431d
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ForEachMacros: [ FOR_EACH, FOR_EACH_ENUMERATE, FOR_EACH_KV, FOR_EACH_R, FOR_EACH_RANGE, FOR_EACH_RANGE_R, ]
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*\.h(pp)?>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
|
@ -130,17 +130,6 @@ ExternalProject_Add(CPPCodec
|
|||
)
|
||||
set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec")
|
||||
|
||||
# CXXOPTS
|
||||
ExternalProject_Add(CxxOpts
|
||||
GIT_REPOSITORY https://github.com/jarro2783/cxxopts
|
||||
GIT_TAG v1.4.4
|
||||
PREFIX cxxopts
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts install step."
|
||||
)
|
||||
set(CXXOPTS_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cxxopts/src/CxxOpts/include")
|
||||
|
||||
# FMT
|
||||
ExternalProject_Add(Fmt
|
||||
PREFIX fmt
|
||||
|
@ -223,6 +212,7 @@ set(LIB_SOURCE_FILES
|
|||
src/utils/Image_Utils.hpp
|
||||
src/utils/String_Utils.cpp
|
||||
src/utils/String_Utils.hpp
|
||||
third_party/CLI11/CLI11.hpp
|
||||
)
|
||||
|
||||
add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES})
|
||||
|
@ -236,7 +226,6 @@ add_dependencies(libFBX2glTF
|
|||
FiFoMap
|
||||
Json
|
||||
STB
|
||||
CxxOpts
|
||||
CPPCodec
|
||||
Fmt
|
||||
)
|
||||
|
@ -300,7 +289,7 @@ if (Iconv_FOUND)
|
|||
endif()
|
||||
|
||||
target_include_directories(appFBX2glTF PUBLIC
|
||||
${CXXOPTS_INCLUDE_DIR}
|
||||
"third_party/CLI11"
|
||||
)
|
||||
target_link_libraries(appFBX2glTF libFBX2glTF)
|
||||
|
||||
|
|
640
src/FBX2glTF.cpp
640
src/FBX2glTF.cpp
|
@ -7,310 +7,392 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if defined( __unix__ ) || defined( __APPLE__ )
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define _stricmp strcasecmp
|
||||
#endif
|
||||
|
||||
#include <cxxopts.hpp>
|
||||
#include <CLI11.hpp>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "utils/String_Utils.hpp"
|
||||
#include "utils/File_Utils.hpp"
|
||||
#include "fbx/Fbx2Raw.hpp"
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
#include "utils/File_Utils.hpp"
|
||||
#include "utils/String_Utils.hpp"
|
||||
|
||||
bool verboseOutput = false;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
cxxopts::Options options(
|
||||
"FBX2glTF",
|
||||
fmt::sprintf("FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION)
|
||||
);
|
||||
int main(int argc, char* argv[]) {
|
||||
GltfOptions gltfOptions;
|
||||
|
||||
std::string inputPath;
|
||||
std::string outputPath;
|
||||
CLI::App app{
|
||||
fmt::sprintf(
|
||||
"FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.",
|
||||
FBX2GLTF_VERSION),
|
||||
"FBX2glTF"};
|
||||
|
||||
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
|
||||
|
||||
GltfOptions gltfOptions;
|
||||
|
||||
options.positional_help("[<FBX File>]");
|
||||
options.add_options()
|
||||
(
|
||||
"i,input", "The FBX model to convert.",
|
||||
cxxopts::value<std::string>(inputPath))
|
||||
(
|
||||
"o,output", "Where to generate the output, without suffix.",
|
||||
cxxopts::value<std::string>(outputPath))
|
||||
(
|
||||
"e,embed", "Inline buffers as data:// URIs within generated non-binary glTF.",
|
||||
cxxopts::value<bool>(gltfOptions.embedResources))
|
||||
(
|
||||
"b,binary", "Output a single binary format .glb file.",
|
||||
cxxopts::value<bool>(gltfOptions.outputBinary))
|
||||
(
|
||||
"long-indices", "Whether to use 32-bit indices (never|auto|always).",
|
||||
cxxopts::value<std::vector<std::string>>())
|
||||
(
|
||||
"d,draco", "Apply Draco mesh compression to geometries.",
|
||||
cxxopts::value<bool>(gltfOptions.draco.enabled))
|
||||
(
|
||||
"draco-compression-level", "The compression level to tune Draco to, from 0 to 10. (default: 7)",
|
||||
cxxopts::value<int>(gltfOptions.draco.compressionLevel))
|
||||
(
|
||||
"draco-bits-for-positions", "How many bits to quantize position to. (default: 14)",
|
||||
cxxopts::value<int>(gltfOptions.draco.quantBitsPosition))
|
||||
(
|
||||
"draco-bits-for-uv", "How many bits to quantize UV coordinates to. (default: 10)",
|
||||
cxxopts::value<int>(gltfOptions.draco.quantBitsTexCoord))
|
||||
(
|
||||
"draco-bits-for-normals", "How many bits to quantize normals to. (default: 10)",
|
||||
cxxopts::value<int>(gltfOptions.draco.quantBitsNormal))
|
||||
(
|
||||
"draco-bits-for-colors", "How many bits to quantize color to. (default: 8)",
|
||||
cxxopts::value<int>(gltfOptions.draco.quantBitsColor))
|
||||
(
|
||||
"draco-bits-for-other", "How many bits to quantize other vertex attributes to to. (default: 8)",
|
||||
cxxopts::value<int>(gltfOptions.draco.quantBitsGeneric))
|
||||
(
|
||||
"compute-normals", "When to compute normals for vertices (never|broken|missing|always).",
|
||||
cxxopts::value<std::vector<std::string>>())
|
||||
("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.",
|
||||
cxxopts::value<bool>(gltfOptions.usePBRMetRough))
|
||||
(
|
||||
"khr-materials-unlit", "Use KHR_materials_unlit extension to specify Unlit shader.",
|
||||
cxxopts::value<bool>(gltfOptions.useKHRMatUnlit))
|
||||
(
|
||||
"no-khr-punctual-lights", "Don't use KHR_punctual_lights extension to export lights.",
|
||||
cxxopts::value<bool>(gltfOptions.useKHRPunctualLights))
|
||||
(
|
||||
"user-properties", "Transcribe FBX User Properties into glTF node and material 'extras'.",
|
||||
cxxopts::value<bool>(gltfOptions.enableUserProperties))
|
||||
(
|
||||
"blend-shape-normals", "Include blend shape normals, if reported present by the FBX SDK.",
|
||||
cxxopts::value<bool>(gltfOptions.useBlendShapeNormals))
|
||||
(
|
||||
"blend-shape-tangents", "Include blend shape tangents, if reported present by the FBX SDK.",
|
||||
cxxopts::value<bool>(gltfOptions.useBlendShapeTangents))
|
||||
(
|
||||
"k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.",
|
||||
cxxopts::value<std::vector<std::string>>())
|
||||
("v,verbose", "Enable verbose output.")
|
||||
("h,help", "Show this help.")
|
||||
("V,version", "Display the current program version.");
|
||||
|
||||
try {
|
||||
options.parse_positional("input");
|
||||
options.parse(argc, argv);
|
||||
|
||||
} catch (const cxxopts::OptionException &e) {
|
||||
fmt::printf(options.help());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (options.count("version")) {
|
||||
fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.count("help")) {
|
||||
fmt::printf(options.help());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!options.count("input")) {
|
||||
fmt::printf("You must supply a FBX file to convert.\n");
|
||||
fmt::printf(options.help());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (options.count("verbose")) {
|
||||
verboseOutput = true;
|
||||
}
|
||||
|
||||
if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n");
|
||||
}
|
||||
gltfOptions.usePBRMetRough = true;
|
||||
}
|
||||
|
||||
if (gltfOptions.draco.compressionLevel != -1 &&
|
||||
(gltfOptions.draco.compressionLevel < 1 || gltfOptions.draco.compressionLevel > 10)) {
|
||||
fmt::printf("Draco compression level must lie in [1, 10].\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.count("flip-u") > 0) {
|
||||
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); });
|
||||
}
|
||||
if (options.count("flip-v") > 0) {
|
||||
fmt::printf("Note: The --flip-v command switch is now default behaviour.\n");
|
||||
}
|
||||
if (options.count("no-flip-v") == 0) {
|
||||
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); });
|
||||
} else if (verboseOutput) {
|
||||
fmt::printf("Suppressing --flip-v transformation of texture coordinates.\n");
|
||||
}
|
||||
|
||||
for (const std::string &choice : options["long-indices"].as<std::vector<std::string>>()) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER;
|
||||
} else if (choice == "auto") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --long-indices: %s\n", choice);
|
||||
fmt::printf(options.help());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.count("compute-normals") > 0) {
|
||||
for (const std::string &choice : options["compute-normals"].as<std::vector<std::string>>()) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::NEVER;
|
||||
} else if (choice == "broken") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::BROKEN;
|
||||
} else if (choice == "missing") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::MISSING;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --compute-normals: %s\n", choice);
|
||||
fmt::printf(options.help());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.count("keep-attribute") > 0) {
|
||||
gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
|
||||
for (std::string attribute : options["keep-attribute"].as<std::vector<std::string>>()) {
|
||||
if (attribute == "position") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; }
|
||||
else if (attribute == "normal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; }
|
||||
else if (attribute == "tangent") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; }
|
||||
else if (attribute == "binormal") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL; }
|
||||
else if (attribute == "color") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR; }
|
||||
else if (attribute == "uv0") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0; }
|
||||
else if (attribute == "uv1") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1; }
|
||||
else if (attribute == "auto") { gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO; }
|
||||
else {
|
||||
fmt::printf("Unknown --keep-attribute: %s\n", attribute);
|
||||
fmt::printf("Valid choices are: position, normal, tangent, binormial, color, uv0, uv1, auto,\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gltfOptions.embedResources && gltfOptions.outputBinary) {
|
||||
fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n");
|
||||
}
|
||||
|
||||
if (options.count("output") == 0) {
|
||||
// if -o is not given, default to the basename of the .fbx
|
||||
outputPath = fmt::format(".{}{}", (const char)StringUtils::GetPathSeparator(), StringUtils::GetFileBaseString(inputPath));
|
||||
|
||||
fmt::printf("outputPath = %s\n", outputPath);
|
||||
}
|
||||
std::string outputFolder; // the output folder in .gltf mode, not used for .glb
|
||||
std::string modelPath; // the path of the actual .glb or .gltf file
|
||||
if (gltfOptions.outputBinary) {
|
||||
// in binary mode, we write precisely where we're asked
|
||||
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";
|
||||
}
|
||||
if (!FileUtils::CreatePath(modelPath.c_str())) {
|
||||
fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
ModelData *data_render_model = nullptr;
|
||||
RawModel raw;
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Loading FBX File: %s\n", inputPath);
|
||||
}
|
||||
if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg")) {
|
||||
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!texturesTransforms.empty()) {
|
||||
raw.TransformTextures(texturesTransforms);
|
||||
}
|
||||
raw.Condense();
|
||||
raw.TransformGeometry(gltfOptions.computeNormals);
|
||||
|
||||
std::ofstream outStream; // note: auto-flushes in destructor
|
||||
const auto streamStart = outStream.tellp();
|
||||
|
||||
outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
|
||||
if (outStream.fail()) {
|
||||
fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions);
|
||||
|
||||
if (gltfOptions.outputBinary) {
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of binary glTF to %s.\n",
|
||||
(unsigned long) (outStream.tellp() - streamStart), modelPath);
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
app.add_flag(
|
||||
"-v,--verbose",
|
||||
verboseOutput,
|
||||
"Include blend shape tangents, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_flag_function("-V,--version", [&](size_t count) {
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of glTF to %s.\n",
|
||||
(unsigned long) (outStream.tellp() - streamStart), modelPath);
|
||||
"FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n",
|
||||
FBX2GLTF_VERSION);
|
||||
exit(0);
|
||||
});
|
||||
|
||||
if (gltfOptions.embedResources) {
|
||||
// we're done: everything was inlined into the glTF JSON
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
std::string inputPath;
|
||||
app.add_option("FBX Model", inputPath, "The FBX model to convert.")
|
||||
->check(CLI::ExistingFile);
|
||||
app.add_option("-i,--input", inputPath, "The FBX model to convert.")
|
||||
->check(CLI::ExistingFile);
|
||||
|
||||
assert(!outputFolder.empty());
|
||||
std::string outputPath;
|
||||
app.add_option(
|
||||
"-o,--output",
|
||||
outputPath,
|
||||
"Where to generate the output, without suffix.");
|
||||
|
||||
const std::string binaryPath = outputFolder + extBufferFilename;
|
||||
FILE *fp = fopen(binaryPath.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
|
||||
return 1;
|
||||
}
|
||||
app.add_flag(
|
||||
"-e,--embed",
|
||||
gltfOptions.embedResources,
|
||||
"Inline buffers as data:// URIs within generated non-binary glTF.");
|
||||
app.add_flag(
|
||||
"-b,--binary",
|
||||
gltfOptions.outputBinary,
|
||||
"Output a single binary format .glb file.");
|
||||
|
||||
if (data_render_model->binary->empty() == false)
|
||||
{
|
||||
const unsigned char *binaryData = &(*data_render_model->binary)[0];
|
||||
unsigned long binarySize = data_render_model->binary->size();
|
||||
if (fwrite(binaryData, binarySize, 1, fp) != 1) {
|
||||
fmt::fprintf(stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
app.add_option(
|
||||
"--long-indices",
|
||||
[&](std::vector<std::string> choices) -> bool {
|
||||
for (const std::string choice : choices) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER;
|
||||
} else if (choice == "auto") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --long-indices: %s\n", choice);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"Whether to use 32-bit indices.")
|
||||
->type_name("(never|auto|always)");
|
||||
|
||||
app.add_option(
|
||||
"--compute-normals",
|
||||
[&](std::vector<std::string> choices) -> bool {
|
||||
for (const std::string choice : choices) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::NEVER;
|
||||
} else if (choice == "broken") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::BROKEN;
|
||||
} else if (choice == "missing") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::MISSING;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --compute-normals option: %s\n", choice);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"When to compute vertex normals from mesh geometry.")
|
||||
->type_name("(never|broken|missing|always)");
|
||||
|
||||
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
|
||||
app.add_flag_function(
|
||||
"--flip-u",
|
||||
[&](size_t count) {
|
||||
if (count > 0) {
|
||||
texturesTransforms.emplace_back(
|
||||
[](Vec2f uv) { return Vec2f(1.0f - uv[0], uv[1]); });
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Flipping texture coordinates in the 'U' dimension.\n");
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
|
||||
}
|
||||
},
|
||||
"Flip all U texture coordinates.");
|
||||
|
||||
app.add_flag("--no-flip-u", "Don't flip U texture coordinates.")
|
||||
->excludes("--flip-u");
|
||||
|
||||
app.add_flag_function(
|
||||
"--no-flip-v",
|
||||
[&](size_t count) {
|
||||
if (count > 0) {
|
||||
texturesTransforms.emplace_back(
|
||||
[](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); });
|
||||
if (verboseOutput) {
|
||||
fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
"Flip all V texture coordinates.");
|
||||
app.add_flag("--flip-v", "Don't flip U texture coordinates.")
|
||||
->excludes("--no-flip-v");
|
||||
|
||||
app.add_flag(
|
||||
"--pbr-metallic-rougnness",
|
||||
gltfOptions.usePBRMetRough,
|
||||
"Try to glean glTF 2.0 native PBR attributes from the FBX.")
|
||||
->group("Materials");
|
||||
|
||||
app.add_flag(
|
||||
"--khr-materials-unlit",
|
||||
gltfOptions.useKHRMatUnlit,
|
||||
"Use KHR_materials_unlit extension to request an unlit shader.")
|
||||
->group("Materials");
|
||||
|
||||
app.add_flag(
|
||||
"--khr-lights-punctual",
|
||||
gltfOptions.useKHRLightsPunctual,
|
||||
"Use KHR_lights_punctual extension to request an unlit shader.");
|
||||
|
||||
app.add_flag(
|
||||
"--user-properties",
|
||||
gltfOptions.enableUserProperties,
|
||||
"Transcribe FBX User Properties into glTF node and material 'extras'.");
|
||||
|
||||
app.add_flag(
|
||||
"--blend-shape-normals",
|
||||
gltfOptions.useBlendShapeNormals,
|
||||
"Include blend shape normals, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_flag(
|
||||
"--blend-shape-tangents",
|
||||
gltfOptions.useBlendShapeTangents,
|
||||
"Include blend shape tangents, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_option(
|
||||
"-k,--keep-attribute",
|
||||
[&](std::vector<std::string> attributes) -> bool {
|
||||
gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES |
|
||||
RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
|
||||
for (std::string attribute : attributes) {
|
||||
if (attribute == "position") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION;
|
||||
} else if (attribute == "normal") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL;
|
||||
} else if (attribute == "tangent") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT;
|
||||
} else if (attribute == "binormal") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL;
|
||||
} else if (attribute == "color") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR;
|
||||
} else if (attribute == "uv0") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0;
|
||||
} else if (attribute == "uv1") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1;
|
||||
} else if (attribute == "auto") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO;
|
||||
} else {
|
||||
fmt::printf("Unknown --keep-attribute option: %s\n", attribute);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"Used repeatedly to build a limiting set of vertex attributes to keep.")
|
||||
->type_size(-1)
|
||||
->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)");
|
||||
|
||||
app.add_flag(
|
||||
"-d,--draco",
|
||||
gltfOptions.draco.enabled,
|
||||
"Apply Draco mesh compression to geometries.")
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-compression-level",
|
||||
gltfOptions.draco.compressionLevel,
|
||||
"The compression level to tune Draco to.",
|
||||
true)
|
||||
->check(CLI::Range(0, 10))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-position",
|
||||
gltfOptions.draco.quantBitsPosition,
|
||||
"How many bits to quantize position to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-uv",
|
||||
gltfOptions.draco.quantBitsTexCoord,
|
||||
"How many bits to quantize UV coordinates to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-normals",
|
||||
gltfOptions.draco.quantBitsNormal,
|
||||
"How many bits to quantize nornals to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-colors",
|
||||
gltfOptions.draco.quantBitsColor,
|
||||
"How many bits to quantize colors to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-other",
|
||||
gltfOptions.draco.quantBitsGeneric,
|
||||
"How many bits to quantize all other vertex attributes to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
if (inputPath.empty()) {
|
||||
fmt::printf("You must supply a FBX file to convert.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n");
|
||||
}
|
||||
gltfOptions.usePBRMetRough = true;
|
||||
}
|
||||
|
||||
if (gltfOptions.embedResources && gltfOptions.outputBinary) {
|
||||
fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
// the output folder in .gltf mode, not used for .glb
|
||||
std::string outputFolder;
|
||||
|
||||
// the path of the actual .glb or .gltf file
|
||||
std::string modelPath;
|
||||
if (gltfOptions.outputBinary) {
|
||||
// in binary mode, we write precisely where we're asked
|
||||
modelPath = outputPath + ".glb";
|
||||
|
||||
} 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";
|
||||
}
|
||||
if (!FileUtils::CreatePath(modelPath.c_str())) {
|
||||
fmt::fprintf(
|
||||
stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
ModelData* data_render_model = nullptr;
|
||||
RawModel raw;
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Loading FBX File: %s\n", inputPath);
|
||||
}
|
||||
if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg")) {
|
||||
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!texturesTransforms.empty()) {
|
||||
raw.TransformTextures(texturesTransforms);
|
||||
}
|
||||
raw.Condense();
|
||||
raw.TransformGeometry(gltfOptions.computeNormals);
|
||||
|
||||
std::ofstream outStream; // note: auto-flushes in destructor
|
||||
const auto streamStart = outStream.tellp();
|
||||
|
||||
outStream.open(
|
||||
modelPath,
|
||||
std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
|
||||
if (outStream.fail()) {
|
||||
fmt::fprintf(
|
||||
stderr,
|
||||
"ERROR:: Couldn't open file for writing: %s\n",
|
||||
modelPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions);
|
||||
|
||||
if (gltfOptions.outputBinary) {
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of binary glTF to %s.\n",
|
||||
(unsigned long)(outStream.tellp() - streamStart),
|
||||
modelPath);
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of glTF to %s.\n",
|
||||
(unsigned long)(outStream.tellp() - streamStart),
|
||||
modelPath);
|
||||
|
||||
if (gltfOptions.embedResources) {
|
||||
// we're done: everything was inlined into the glTF JSON
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(!outputFolder.empty());
|
||||
|
||||
const std::string binaryPath = outputFolder + extBufferFilename;
|
||||
FILE* fp = fopen(binaryPath.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
fmt::fprintf(
|
||||
stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data_render_model->binary->empty() == false) {
|
||||
const unsigned char* binaryData = &(*data_render_model->binary)[0];
|
||||
unsigned long binarySize = data_render_model->binary->size();
|
||||
if (fwrite(binaryData, binarySize, 1, fp) != 1) {
|
||||
fmt::fprintf(
|
||||
stderr,
|
||||
"ERROR: Failed to write %lu bytes to file '%s'.\n",
|
||||
binarySize,
|
||||
binaryPath);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
fclose(fp);
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
|
||||
}
|
||||
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -76,12 +76,12 @@ struct GltfOptions
|
|||
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int compressionLevel = -1;
|
||||
int quantBitsPosition = -1;
|
||||
int quantBitsTexCoord = -1;
|
||||
int quantBitsNormal = -1;
|
||||
int quantBitsColor = -1;
|
||||
int quantBitsGeneric = -1;
|
||||
int compressionLevel = 7;
|
||||
int quantBitsPosition = 14;
|
||||
int quantBitsTexCoord = 10;
|
||||
int quantBitsNormal = 10;
|
||||
int quantBitsColor = 8;
|
||||
int quantBitsGeneric = 8;
|
||||
} draco;
|
||||
|
||||
/** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */
|
||||
|
@ -93,7 +93,7 @@ struct GltfOptions
|
|||
bool usePBRMetRough { false };
|
||||
|
||||
/** Whether to include lights through the KHR_punctual_lights extension. */
|
||||
bool useKHRPunctualLights { true };
|
||||
bool useKHRLightsPunctual { true };
|
||||
|
||||
/** Whether to include blend shape normals, if present according to the SDK. */
|
||||
bool useBlendShapeNormals { false };
|
||||
|
|
|
@ -631,7 +631,7 @@ ModelData *Raw2Gltf(
|
|||
// lights
|
||||
//
|
||||
std::vector<json> khrPunctualLights;
|
||||
if (options.useKHRPunctualLights) {
|
||||
if (options.useKHRLightsPunctual) {
|
||||
for (int i = 0; i < raw.GetLightCount(); i ++) {
|
||||
const RawLight &light = raw.GetLight(i);
|
||||
LightData::Type type;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue