FBX2glTF/src/main.cpp

245 lines
8.9 KiB
C++

/**
* 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 <vector>
#include <unordered_map>
#include <map>
#include <iostream>
#include <fstream>
#if defined( __unix__ ) || defined( __APPLE__ )
#include <sys/stat.h>
#define _stricmp strcasecmp
#endif
#include <cxxopts.hpp>
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "utils/File_Utils.h"
#include "Fbx2Raw.h"
#include "RawModel.h"
#include "Raw2Gltf.h"
bool verboseOutput = false;
int main(int argc, char *argv[])
{
cxxopts::Options options(
"FBX2glTF",
"FBX2glTF 2.0: Generate a glTF 2.0 representation of an FBX model.");
std::string inputPath;
std::string outputPath;
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
GltfOptions gltfOptions{
-1, // keepAttribs
false, // outputBinary
false, // embedResources
false, // useDraco
false, // useKHRMatCom
false, // usePBRMetRough
false // usePBRSpecGloss
};
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))
(
"d,draco", "Apply Draco mesh compression to geometries.",
cxxopts::value<bool>(gltfOptions.useDraco))
("flip-u", "Flip all U texture coordinates.")
("flip-v", "Flip all V texture coordinates.")
(
"khr-materials-common", "(WIP) Use KHR_materials_common extensions to specify Unlit/Lambert/Blinn/Phong shaders.",
cxxopts::value<bool>(gltfOptions.useKHRMatCom))
(
"pbr-metallic-roughness", "(WIP) Try to glean glTF 2.0 native PBR attributes from the FBX.",
cxxopts::value<bool>(gltfOptions.usePBRMetRough))
(
"pbr-specular-glossiness", "(WIP) Experimentally fill in the KHR_materials_pbrSpecularGlossiness extension.",
cxxopts::value<bool>(gltfOptions.usePBRSpecGloss))
(
"k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.",
cxxopts::value<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(
R"(
FBX2glTF version 2.0
Copyright (c) 2016-2017 Oculus VR, LLC.
)");
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.useKHRMatCom && !gltfOptions.usePBRSpecGloss && !gltfOptions.usePBRMetRough) {
if (verboseOutput) {
fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n");
}
gltfOptions.usePBRMetRough = true;
}
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) {
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0f - uv[1]); });
}
if (options.count("keepAttribute")) {
gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
for (const auto &attribute : options["keepAttribute"].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 --keepAttribute: %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 = "./" + Gltf::StringUtils::GetFileBaseString(inputPath);
}
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 = outputPath + "_out/";
modelPath = outputFolder + Gltf::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(), "tga;bmp;png;jpg")) {
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
return 1;
}
if (!texturesTransforms.empty()) {
raw.TransformTextures(texturesTransforms);
}
raw.Condense();
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, 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;
}
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;
}