|
|
@@ -1,805 +1,803 @@
|
|
|
-#include "Base.h"
|
|
|
-
|
|
|
-#include "EncoderArguments.h"
|
|
|
-#include "StringUtil.h"
|
|
|
-
|
|
|
-#ifdef WIN32
|
|
|
- #define PATH_MAX _MAX_PATH
|
|
|
- #define realpath(A,B) _fullpath(B,A,PATH_MAX)
|
|
|
-#endif
|
|
|
-
|
|
|
-// The encoder version number should be incremented when a feature is added to the encoder.
|
|
|
-// The encoder version is not the same as the GPB version.
|
|
|
-#define ENCODER_VERSION "2.0.0"
|
|
|
-#define HEIGHTMAP_SIZE_MAX 2049
|
|
|
-
|
|
|
-namespace gameplay
|
|
|
-{
|
|
|
-
|
|
|
-static EncoderArguments* __instance;
|
|
|
-
|
|
|
-extern int __logVerbosity = 1;
|
|
|
-
|
|
|
-EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
|
|
|
- _normalMap(false),
|
|
|
- _parseError(false),
|
|
|
- _fontPreview(false),
|
|
|
- _fontFormat(Font::BITMAP),
|
|
|
- _textOutput(false),
|
|
|
- _optimizeAnimations(false),
|
|
|
- _animationGrouping(ANIMATIONGROUP_PROMPT),
|
|
|
- _outputMaterial(false)
|
|
|
-{
|
|
|
- __instance = this;
|
|
|
-
|
|
|
- memset(_heightmapResolution, 0, sizeof(int) * 2);
|
|
|
-
|
|
|
- if (argc > 1)
|
|
|
- {
|
|
|
- // read the options
|
|
|
- std::vector<std::string> arguments;
|
|
|
- for (size_t i = 1; i < argc; ++i)
|
|
|
- {
|
|
|
- arguments.push_back(argv[i]);
|
|
|
- }
|
|
|
- size_t index = 0;
|
|
|
- for (size_t i = 0; i < arguments.size(); ++i)
|
|
|
- {
|
|
|
- if (arguments[i][0] == '-')
|
|
|
- {
|
|
|
- readOption(arguments, &i);
|
|
|
- index = i + 1;
|
|
|
- }
|
|
|
- }
|
|
|
- if (arguments.size() - index == 2)
|
|
|
- {
|
|
|
- setInputfilePath(arguments[index]);
|
|
|
- setOutputfilePath(arguments[index + 1]);
|
|
|
- }
|
|
|
- else if (arguments.size() - index == 1)
|
|
|
- {
|
|
|
- setInputfilePath(arguments[index]);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _parseError = true;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-EncoderArguments::~EncoderArguments(void)
|
|
|
-{
|
|
|
-}
|
|
|
-
|
|
|
-EncoderArguments* EncoderArguments::getInstance()
|
|
|
-{
|
|
|
- return __instance;
|
|
|
-}
|
|
|
-
|
|
|
-const std::string& EncoderArguments::getFilePath() const
|
|
|
-{
|
|
|
- return _filePath;
|
|
|
-}
|
|
|
-
|
|
|
-const char* EncoderArguments::getFilePathPointer() const
|
|
|
-{
|
|
|
- return _filePath.c_str();
|
|
|
-}
|
|
|
-
|
|
|
-std::string EncoderArguments::getOutputDirPath() const
|
|
|
-{
|
|
|
- if (_fileOutputPath.size() > 0)
|
|
|
- {
|
|
|
- int pos = _fileOutputPath.find_last_of('/');
|
|
|
- return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- int pos = _filePath.find_last_of('/');
|
|
|
- return (pos == -1 ? _filePath : _filePath.substr(0, pos));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-std::string EncoderArguments::getOutputFileExtension() const
|
|
|
-{
|
|
|
- switch (getFileFormat())
|
|
|
- {
|
|
|
- case FILEFORMAT_PNG:
|
|
|
- case FILEFORMAT_RAW:
|
|
|
- if (_normalMap)
|
|
|
- return ".png";
|
|
|
-
|
|
|
- default:
|
|
|
- return ".gpb";
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-std::string EncoderArguments::getOutputFilePath() const
|
|
|
-{
|
|
|
- if (_fileOutputPath.size() > 0)
|
|
|
- {
|
|
|
- // Output file explicitly set
|
|
|
- return _fileOutputPath;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Generate an output file path
|
|
|
- int pos = _filePath.find_last_of('.');
|
|
|
- std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);
|
|
|
-
|
|
|
- // Modify the original file name if the output extension can be the same as the input
|
|
|
- if (_normalMap)
|
|
|
- {
|
|
|
- outputFilePath.append("_normalmap");
|
|
|
- }
|
|
|
-
|
|
|
- outputFilePath.append(getOutputFileExtension());
|
|
|
- return outputFilePath;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const std::vector<std::string>& EncoderArguments::getGroupAnimationNodeId() const
|
|
|
-{
|
|
|
- return _groupAnimationNodeId;
|
|
|
-}
|
|
|
-
|
|
|
-const std::vector<std::string>& EncoderArguments::getGroupAnimationAnimationId() const
|
|
|
-{
|
|
|
- return _groupAnimationAnimationId;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::containsGroupNodeId(const std::string& nodeId) const
|
|
|
-{
|
|
|
- return find(_groupAnimationNodeId.begin(), _groupAnimationNodeId.end(), nodeId) != _groupAnimationNodeId.end();
|
|
|
-}
|
|
|
-
|
|
|
-const std::string EncoderArguments::getAnimationId(const std::string& nodeId) const
|
|
|
-{
|
|
|
- for (size_t i = 0, size = _groupAnimationNodeId.size(); i < size; ++i)
|
|
|
- {
|
|
|
- if (_groupAnimationNodeId[i].compare(nodeId) == 0)
|
|
|
- {
|
|
|
- return _groupAnimationAnimationId[i];
|
|
|
- }
|
|
|
- }
|
|
|
- return "";
|
|
|
-}
|
|
|
-
|
|
|
-EncoderArguments::AnimationGroupOption EncoderArguments::getAnimationGrouping() const
|
|
|
-{
|
|
|
- return _animationGrouping;
|
|
|
-}
|
|
|
-
|
|
|
-const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeightmapOptions() const
|
|
|
-{
|
|
|
- return _heightmaps;
|
|
|
-}
|
|
|
-
|
|
|
-unsigned int EncoderArguments::tangentBinormalIdCount() const
|
|
|
-{
|
|
|
- return _tangentBinormalId.size();
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::isGenerateTangentBinormalId(const std::string& id) const
|
|
|
-{
|
|
|
- return _tangentBinormalId.find(id) != _tangentBinormalId.end();
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::normalMapGeneration() const
|
|
|
-{
|
|
|
- return _normalMap;
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::getHeightmapResolution(int* x, int* y) const
|
|
|
-{
|
|
|
- *x = _heightmapResolution[0];
|
|
|
- *y = _heightmapResolution[1];
|
|
|
-}
|
|
|
-
|
|
|
-const Vector3& EncoderArguments::getHeightmapWorldSize() const
|
|
|
-{
|
|
|
- return _heightmapWorldSize;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::parseErrorOccured() const
|
|
|
-{
|
|
|
- return _parseError;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::fileExists() const
|
|
|
-{
|
|
|
- if (_filePath.length() > 0)
|
|
|
- {
|
|
|
- struct stat buf;
|
|
|
- if (stat(_filePath.c_str(), &buf) != -1)
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
-}
|
|
|
-
|
|
|
-void splitString(const char* str, std::vector<std::string>* tokens)
|
|
|
-{
|
|
|
- // Split node id list into tokens
|
|
|
- unsigned int length = strlen(str);
|
|
|
- char* temp = new char[length + 1];
|
|
|
- strcpy(temp, str);
|
|
|
- char* tok = strtok(temp, ",");
|
|
|
- while (tok)
|
|
|
- {
|
|
|
- tokens->push_back(tok);
|
|
|
- tok = strtok(NULL, ",");
|
|
|
- }
|
|
|
- delete[] temp;
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::printUsage() const
|
|
|
-{
|
|
|
- LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n" \
|
|
|
- "Supported file extensions:\n" \
|
|
|
- " .fbx\t(FBX)\n" \
|
|
|
- " .ttf\t(TrueType Font)\n" \
|
|
|
- "\n" \
|
|
|
- "General Options:\n" \
|
|
|
- " -v <verbosity>\tVerbosity level (0-4).\n" \
|
|
|
- "\n" \
|
|
|
- "FBX file options:\n" \
|
|
|
- " -i <id>\tFilter by node ID.\n" \
|
|
|
- " -t\t\tWrite text/xml.\n" \
|
|
|
- " -g:auto\tAutomatically group animation channels into a new animation.\n" \
|
|
|
- " -g:none\tDo not prompt to group animations.\n" \
|
|
|
- " -g <node id> <animation id>\n" \
|
|
|
- "\t\tGroup all animation channels targeting the nodes into a \n" \
|
|
|
- "\t\tnew animation.\n" \
|
|
|
- " -m\t\tOutput material file for scene.\n" \
|
|
|
- " -tb <node id>\n" \
|
|
|
- "\t\tGenerates tangents and binormals for the given node.\n" \
|
|
|
- " -oa\n" \
|
|
|
- "\t\tOptimizes animations by analyzing animation channel data and\n" \
|
|
|
- "\t\tremoving any channels that contain default/identity values\n" \
|
|
|
- "\t\tand removing any duplicate contiguous keyframes, which are \n" \
|
|
|
- "\t\tcommon when exporting baked animation data.\n" \
|
|
|
- " -h <size> \"<node ids>\" <filename>\n" \
|
|
|
- "\t\tGenerates a single heightmap image using meshes from the \n" \
|
|
|
- "\t\tspecified nodes. \n" \
|
|
|
- "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
|
|
|
- "\t\tindicating the dimensions of the produced heightmap image.\n" \
|
|
|
- "\t\t<node ids> should be in quotes with a space between each id.\n" \
|
|
|
- "\t\tFilename is the name of the image (PNG) to be saved.\n" \
|
|
|
- "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
|
|
|
- "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
|
|
|
- "\n" \
|
|
|
- "Normal map generation options:\n" \
|
|
|
- " -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
|
|
|
- " -s\t\tSize/resolution of the input heightmap image \n" \
|
|
|
- " \t\t(required for RAW files)\n" \
|
|
|
- " -w <size>\tSpecifies the size of an input terrain heightmap file in world\n" \
|
|
|
- "\t\tunits, along the X, Y and Z axes. <size> should be three \n" \
|
|
|
- "\t\tcomma-separated numbers in the format \"X,Y,Z\". The Y value \n" \
|
|
|
- "\t\trepresents the maximum possible height value of a full \n" \
|
|
|
- "\t\tintensity heightmap pixel.\n" \
|
|
|
- "\n" \
|
|
|
- " Normal map generation can be used to create object-space normal maps from \n" \
|
|
|
- " heightmap images. Heightmaps must be in either PNG format (where the \n" \
|
|
|
- " intensity of each pixel represents a height value), or in RAW format \n" \
|
|
|
- " (8 or 16-bit), which is a common headerless format supported by most \n" \
|
|
|
- " terrain generation tools.\n" \
|
|
|
- "\n" \
|
|
|
- "TTF file options:\n" \
|
|
|
- " -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
|
|
|
- " -p\t\tOutput font preview.\n" \
|
|
|
- " -f Format of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
|
|
|
- "\n" \
|
|
|
- "Encoder version: " ENCODER_VERSION "\n" \
|
|
|
- "\n");
|
|
|
- exit(8);
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::fontPreviewEnabled() const
|
|
|
-{
|
|
|
- return _fontPreview;
|
|
|
-}
|
|
|
-
|
|
|
-Font::FontFormat EncoderArguments::getFontFormat() const
|
|
|
-{
|
|
|
- return _fontFormat;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::textOutputEnabled() const
|
|
|
-{
|
|
|
- return _textOutput;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::optimizeAnimationsEnabled() const
|
|
|
-{
|
|
|
- return _optimizeAnimations;
|
|
|
-}
|
|
|
-
|
|
|
-bool EncoderArguments::outputMaterialEnabled() const
|
|
|
-{
|
|
|
- return _outputMaterial;
|
|
|
-}
|
|
|
-
|
|
|
-const char* EncoderArguments::getNodeId() const
|
|
|
-{
|
|
|
- if (_nodeId.length() == 0)
|
|
|
- {
|
|
|
- return NULL;
|
|
|
- }
|
|
|
- return _nodeId.c_str();
|
|
|
-}
|
|
|
-
|
|
|
-std::vector<unsigned int> EncoderArguments::getFontSizes() const
|
|
|
-{
|
|
|
- return _fontSizes;
|
|
|
-}
|
|
|
-
|
|
|
-EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
|
|
|
-{
|
|
|
- if (_filePath.length() < 5)
|
|
|
- {
|
|
|
- return FILEFORMAT_UNKNOWN;
|
|
|
- }
|
|
|
- // Extract the extension
|
|
|
- std::string ext = "";
|
|
|
- size_t pos = _filePath.find_last_of(".");
|
|
|
- if (pos != std::string::npos)
|
|
|
- {
|
|
|
- ext = _filePath.substr(pos + 1);
|
|
|
- }
|
|
|
- for (size_t i = 0; i < ext.size(); ++i)
|
|
|
- ext[i] = (char)tolower(ext[i]);
|
|
|
-
|
|
|
- // Match every supported extension with its format constant
|
|
|
- if (ext.compare("dae") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_DAE;
|
|
|
- }
|
|
|
- if (ext.compare("fbx") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_FBX;
|
|
|
- }
|
|
|
- if (ext.compare("ttf") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_TTF;
|
|
|
- }
|
|
|
- if (ext.compare("gpb") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_GPB;
|
|
|
- }
|
|
|
- if (ext.compare("png") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_PNG;
|
|
|
- }
|
|
|
- if (ext.compare("raw") == 0)
|
|
|
- {
|
|
|
- return FILEFORMAT_RAW;
|
|
|
- }
|
|
|
-
|
|
|
- return FILEFORMAT_UNKNOWN;
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
|
|
|
-{
|
|
|
- const std::string& str = options[*index];
|
|
|
- if (str.length() == 0 && str[0] != '-')
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- switch (str[1])
|
|
|
- {
|
|
|
- case 'f':
|
|
|
- if (str.compare("-f:b") == 0)
|
|
|
- {
|
|
|
- _fontFormat = Font::BITMAP;
|
|
|
- }
|
|
|
- else if (str.compare("-f:d") == 0)
|
|
|
- {
|
|
|
- _fontFormat = Font::DISTANCE_FIELD;
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'g':
|
|
|
- if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
|
|
|
- {
|
|
|
- _animationGrouping = ANIMATIONGROUP_AUTO;
|
|
|
- }
|
|
|
- else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
|
|
|
- {
|
|
|
- _animationGrouping = ANIMATIONGROUP_OFF;
|
|
|
- }
|
|
|
- else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
|
|
|
- {
|
|
|
- // read two strings, make sure not to go out of bounds
|
|
|
- if ((*index + 2) >= options.size())
|
|
|
- {
|
|
|
- LOG(1, "Error: -g requires 2 arguments.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- (*index)++;
|
|
|
- _groupAnimationNodeId.push_back(options[*index]);
|
|
|
- (*index)++;
|
|
|
- _groupAnimationAnimationId.push_back(options[*index]);
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'i':
|
|
|
- // Node ID
|
|
|
- (*index)++;
|
|
|
- if (*index < options.size())
|
|
|
- {
|
|
|
- _nodeId.assign(options[*index]);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'o':
|
|
|
- // Optimization flag
|
|
|
- if (str == "-oa")
|
|
|
- {
|
|
|
- // Optimize animations
|
|
|
- _optimizeAnimations = true;
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'h':
|
|
|
- {
|
|
|
- bool isHighPrecision = str.compare("-hp") == 0;
|
|
|
- if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
|
|
|
- {
|
|
|
- (*index)++;
|
|
|
- if (*index < (options.size() + 2))
|
|
|
- {
|
|
|
- _heightmaps.resize(_heightmaps.size() + 1);
|
|
|
- HeightmapOption& heightmap = _heightmaps.back();
|
|
|
-
|
|
|
- heightmap.isHighPrecision = isHighPrecision;
|
|
|
-
|
|
|
- // Read heightmap size
|
|
|
- std::vector<std::string> parts;
|
|
|
- splitString(options[*index].c_str(), &parts);
|
|
|
- if (parts.size() != 2)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- heightmap.width = atoi(parts[0].c_str());
|
|
|
- heightmap.height = atoi(parts[1].c_str());
|
|
|
-
|
|
|
- // Put some artificial bounds on heightmap dimensions
|
|
|
- if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
|
|
|
- {
|
|
|
- LOG(1, "Error: size argument for -h|-heightmap must be between (1,1) and (%d,%d).\n", (int)HEIGHTMAP_SIZE_MAX, (int)HEIGHTMAP_SIZE_MAX);
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Split node id list into tokens
|
|
|
- (*index)++;
|
|
|
- splitString(options[*index].c_str(), &heightmap.nodeIds);
|
|
|
-
|
|
|
- // Store output filename
|
|
|
- (*index)++;
|
|
|
- heightmap.filename = options[*index];
|
|
|
- if (heightmap.filename.empty())
|
|
|
- {
|
|
|
- LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Ensure the output filename has a .png extention
|
|
|
- if (heightmap.filename.length() > 5)
|
|
|
- {
|
|
|
- const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
|
|
|
- if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
|
|
|
- heightmap.filename += ".png";
|
|
|
- }
|
|
|
- else
|
|
|
- heightmap.filename += ".png";
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- LOG(1, "Error: missing argument for -h|-heightmap.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'm':
|
|
|
- if (str.compare("-m") == 0)
|
|
|
- {
|
|
|
- // generate a material file
|
|
|
- _outputMaterial = true;
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'n':
|
|
|
- _normalMap = true;
|
|
|
- break;
|
|
|
- case 'w':
|
|
|
- {
|
|
|
- // Read world size
|
|
|
- (*index)++;
|
|
|
- if (*index >= options.size())
|
|
|
- {
|
|
|
- LOG(1, "Error: missing world size argument for -w.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- std::vector<std::string> parts;
|
|
|
- splitString(options[*index].c_str(), &parts);
|
|
|
- if (parts.size() != 3)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid world size argument for -w.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- _heightmapWorldSize.x = (float)atof(parts[0].c_str());
|
|
|
- _heightmapWorldSize.y = (float)atof(parts[1].c_str());
|
|
|
- _heightmapWorldSize.z = (float)atof(parts[2].c_str());
|
|
|
- if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid world size argument for -w.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'p':
|
|
|
- _fontPreview = true;
|
|
|
- break;
|
|
|
- case 's':
|
|
|
- if (_normalMap)
|
|
|
- {
|
|
|
- (*index)++;
|
|
|
- if (*index >= options.size())
|
|
|
- {
|
|
|
- LOG(1, "Error: missing argument for -s.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- // Heightmap size
|
|
|
- std::vector<std::string> parts;
|
|
|
- splitString(options[*index].c_str(), &parts);
|
|
|
- if (parts.size() != 2 ||
|
|
|
- (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
|
|
|
- (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid argument for -s.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Font Sizes
|
|
|
- // old format was -s##
|
|
|
- const char* sizes = NULL;
|
|
|
- if (str.length() > 2)
|
|
|
- {
|
|
|
- char n = str[2];
|
|
|
- if (n > '0' && n <= '9')
|
|
|
- {
|
|
|
- sizes = str.c_str() + 2;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- (*index)++;
|
|
|
- if (*index < options.size())
|
|
|
- {
|
|
|
- sizes = options[*index].c_str();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (sizes == NULL)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid format for argument: -s");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Parse comma-separated list of font sizes
|
|
|
- char* ptr = const_cast<char*>(sizes);
|
|
|
- std::string sizeStr;
|
|
|
- while (ptr)
|
|
|
- {
|
|
|
- char* end = strchr(ptr, ',');
|
|
|
- if (end)
|
|
|
- {
|
|
|
- sizeStr = std::string(ptr, end - ptr);
|
|
|
- ptr = end + 1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- sizeStr = ptr;
|
|
|
- ptr = NULL;
|
|
|
- }
|
|
|
- if (sizeStr.length() > 0)
|
|
|
- {
|
|
|
- int size = atoi(sizeStr.c_str());
|
|
|
- if (size <= 0)
|
|
|
- {
|
|
|
- LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- _fontSizes.push_back((unsigned int)size);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case 't':
|
|
|
- if (str.compare("-t") == 0)
|
|
|
- {
|
|
|
- _textOutput = true;
|
|
|
- }
|
|
|
- else if (str.compare("-tb") == 0)
|
|
|
- {
|
|
|
- if ((*index + 1) >= options.size())
|
|
|
- {
|
|
|
- LOG(1, "Error: -tb requires 1 argument.\n");
|
|
|
- _parseError = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- (*index)++;
|
|
|
- std::string nodeId = options[*index];
|
|
|
- if (nodeId.length() > 0)
|
|
|
- {
|
|
|
- _tangentBinormalId.insert(nodeId);
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'v':
|
|
|
- (*index)++;
|
|
|
- if (*index < options.size())
|
|
|
- {
|
|
|
- __logVerbosity = atoi(options[*index].c_str());
|
|
|
- if (__logVerbosity < 0)
|
|
|
- __logVerbosity = 0;
|
|
|
- else if (__logVerbosity > 4)
|
|
|
- __logVerbosity = 4;
|
|
|
- }
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::setInputfilePath(const std::string& inputPath)
|
|
|
-{
|
|
|
- _filePath.assign(getRealPath(inputPath));
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::setOutputfilePath(const std::string& outputPath)
|
|
|
-{
|
|
|
- std::string ext = getOutputFileExtension();
|
|
|
-
|
|
|
- if (outputPath.size() > 0 && outputPath[0] != '\0')
|
|
|
- {
|
|
|
- std::string realPath = getRealPath(outputPath);
|
|
|
- if (endsWith(realPath.c_str(), ext.c_str()))
|
|
|
- {
|
|
|
- _fileOutputPath.assign(realPath);
|
|
|
- }
|
|
|
- else if (endsWith(outputPath.c_str(), "/"))
|
|
|
- {
|
|
|
- std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));
|
|
|
-
|
|
|
- _fileOutputPath.assign(outputPath);
|
|
|
- _fileOutputPath.append(filenameNoExt);
|
|
|
- _fileOutputPath.append(ext);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
|
|
|
- int pos = realPath.find_last_of("/");
|
|
|
- if (pos)
|
|
|
- {
|
|
|
- _fileOutputPath = realPath.substr(0, pos);
|
|
|
- _fileOutputPath.append("/");
|
|
|
- _fileOutputPath.append(filenameNoExt);
|
|
|
- _fileOutputPath.append(ext);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-std::string EncoderArguments::getRealPath(const std::string& filepath)
|
|
|
-{
|
|
|
- char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
|
|
|
- realpath(filepath.c_str(), path);
|
|
|
- replace_char(path, '\\', '/');
|
|
|
- return std::string(path);
|
|
|
-}
|
|
|
-
|
|
|
-void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
|
|
|
-{
|
|
|
- for (; *str != '\0'; ++str)
|
|
|
- {
|
|
|
- if (*str == oldChar)
|
|
|
- {
|
|
|
- *str = newChar;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-std::string concat(const std::string& a, const char* b)
|
|
|
-{
|
|
|
- std::string str(a);
|
|
|
- str.append(b);
|
|
|
- return str;
|
|
|
-}
|
|
|
-
|
|
|
-void unittestsEncoderArguments()
|
|
|
-{
|
|
|
- std::string dir = EncoderArguments::getRealPath(".");
|
|
|
- std::string exePath = EncoderArguments::getRealPath(".");
|
|
|
- exePath.append("/gameplay-encoder.exe");
|
|
|
- const char* exe = exePath.c_str();
|
|
|
- {
|
|
|
- const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getAnimationId("root"), ("movements")));
|
|
|
- assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
|
|
|
- assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
|
|
|
- }
|
|
|
- {
|
|
|
- // Test with only input file name (relative)
|
|
|
- const char* argv[] = {exe, "input.fbx"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
|
|
|
- equals(args.getOutputDirPath(), dir);
|
|
|
- }
|
|
|
- {
|
|
|
- // Test specifying a relative output path
|
|
|
- const char* argv[] = {exe, "input.fbx", "output.gpb"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
- }
|
|
|
- {
|
|
|
- // Test specifying a relative output path
|
|
|
- const char* argv[] = {exe, "input.fbx", "output.gpb"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
- }
|
|
|
- {
|
|
|
- // Test specifying a relative output path to a directory
|
|
|
- const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
|
|
|
- }
|
|
|
- {
|
|
|
- // Test parsing some arguments
|
|
|
- const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
- assert(args.textOutputEnabled());
|
|
|
- }
|
|
|
- {
|
|
|
- // Test output file with no file extension
|
|
|
- const char* argv[] = {exe, "input.fbx", "output"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
- }
|
|
|
- {
|
|
|
- // Test output file with wrong file extension
|
|
|
- const char* argv[] = {exe, "input.fbx", "output.fbx"};
|
|
|
- EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
- assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
- assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-}
|
|
|
+#include "Base.h"
|
|
|
+
|
|
|
+#include "EncoderArguments.h"
|
|
|
+#include "StringUtil.h"
|
|
|
+
|
|
|
+#ifdef WIN32
|
|
|
+ #define PATH_MAX _MAX_PATH
|
|
|
+ #define realpath(A,B) _fullpath(B,A,PATH_MAX)
|
|
|
+#endif
|
|
|
+
|
|
|
+// The encoder version number should be incremented when a feature is added to the encoder.
|
|
|
+// The encoder version is not the same as the GPB version.
|
|
|
+#define ENCODER_VERSION "2.0.0"
|
|
|
+#define HEIGHTMAP_SIZE_MAX 2049
|
|
|
+
|
|
|
+namespace gameplay
|
|
|
+{
|
|
|
+
|
|
|
+static EncoderArguments* __instance;
|
|
|
+
|
|
|
+extern int __logVerbosity = 1;
|
|
|
+
|
|
|
+EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
|
|
|
+ _normalMap(false),
|
|
|
+ _parseError(false),
|
|
|
+ _fontPreview(false),
|
|
|
+ _fontFormat(Font::BITMAP),
|
|
|
+ _textOutput(false),
|
|
|
+ _optimizeAnimations(false),
|
|
|
+ _animationGrouping(ANIMATIONGROUP_PROMPT),
|
|
|
+ _outputMaterial(false)
|
|
|
+{
|
|
|
+ __instance = this;
|
|
|
+
|
|
|
+ memset(_heightmapResolution, 0, sizeof(int) * 2);
|
|
|
+
|
|
|
+ if (argc > 1)
|
|
|
+ {
|
|
|
+ // read the options
|
|
|
+ std::vector<std::string> arguments;
|
|
|
+ for (size_t i = 1; i < argc; ++i)
|
|
|
+ {
|
|
|
+ arguments.push_back(argv[i]);
|
|
|
+ }
|
|
|
+ size_t index = 0;
|
|
|
+ for (size_t i = 0; i < arguments.size(); ++i)
|
|
|
+ {
|
|
|
+ if (arguments[i][0] == '-')
|
|
|
+ {
|
|
|
+ readOption(arguments, &i);
|
|
|
+ index = i + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (arguments.size() - index == 2)
|
|
|
+ {
|
|
|
+ setInputfilePath(arguments[index]);
|
|
|
+ setOutputfilePath(arguments[index + 1]);
|
|
|
+ }
|
|
|
+ else if (arguments.size() - index == 1)
|
|
|
+ {
|
|
|
+ setInputfilePath(arguments[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ _parseError = true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+EncoderArguments::~EncoderArguments(void)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+EncoderArguments* EncoderArguments::getInstance()
|
|
|
+{
|
|
|
+ return __instance;
|
|
|
+}
|
|
|
+
|
|
|
+const std::string& EncoderArguments::getFilePath() const
|
|
|
+{
|
|
|
+ return _filePath;
|
|
|
+}
|
|
|
+
|
|
|
+const char* EncoderArguments::getFilePathPointer() const
|
|
|
+{
|
|
|
+ return _filePath.c_str();
|
|
|
+}
|
|
|
+
|
|
|
+std::string EncoderArguments::getOutputDirPath() const
|
|
|
+{
|
|
|
+ if (_fileOutputPath.size() > 0)
|
|
|
+ {
|
|
|
+ int pos = _fileOutputPath.find_last_of('/');
|
|
|
+ return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int pos = _filePath.find_last_of('/');
|
|
|
+ return (pos == -1 ? _filePath : _filePath.substr(0, pos));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::string EncoderArguments::getOutputFileExtension() const
|
|
|
+{
|
|
|
+ switch (getFileFormat())
|
|
|
+ {
|
|
|
+ case FILEFORMAT_PNG:
|
|
|
+ case FILEFORMAT_RAW:
|
|
|
+ if (_normalMap)
|
|
|
+ return ".png";
|
|
|
+
|
|
|
+ default:
|
|
|
+ return ".gpb";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::string EncoderArguments::getOutputFilePath() const
|
|
|
+{
|
|
|
+ if (_fileOutputPath.size() > 0)
|
|
|
+ {
|
|
|
+ // Output file explicitly set
|
|
|
+ return _fileOutputPath;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Generate an output file path
|
|
|
+ int pos = _filePath.find_last_of('.');
|
|
|
+ std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);
|
|
|
+
|
|
|
+ // Modify the original file name if the output extension can be the same as the input
|
|
|
+ if (_normalMap)
|
|
|
+ {
|
|
|
+ outputFilePath.append("_normalmap");
|
|
|
+ }
|
|
|
+
|
|
|
+ outputFilePath.append(getOutputFileExtension());
|
|
|
+ return outputFilePath;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const std::vector<std::string>& EncoderArguments::getGroupAnimationNodeId() const
|
|
|
+{
|
|
|
+ return _groupAnimationNodeId;
|
|
|
+}
|
|
|
+
|
|
|
+const std::vector<std::string>& EncoderArguments::getGroupAnimationAnimationId() const
|
|
|
+{
|
|
|
+ return _groupAnimationAnimationId;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::containsGroupNodeId(const std::string& nodeId) const
|
|
|
+{
|
|
|
+ return find(_groupAnimationNodeId.begin(), _groupAnimationNodeId.end(), nodeId) != _groupAnimationNodeId.end();
|
|
|
+}
|
|
|
+
|
|
|
+const std::string EncoderArguments::getAnimationId(const std::string& nodeId) const
|
|
|
+{
|
|
|
+ for (size_t i = 0, size = _groupAnimationNodeId.size(); i < size; ++i)
|
|
|
+ {
|
|
|
+ if (_groupAnimationNodeId[i].compare(nodeId) == 0)
|
|
|
+ {
|
|
|
+ return _groupAnimationAnimationId[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
+EncoderArguments::AnimationGroupOption EncoderArguments::getAnimationGrouping() const
|
|
|
+{
|
|
|
+ return _animationGrouping;
|
|
|
+}
|
|
|
+
|
|
|
+const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeightmapOptions() const
|
|
|
+{
|
|
|
+ return _heightmaps;
|
|
|
+}
|
|
|
+
|
|
|
+unsigned int EncoderArguments::tangentBinormalIdCount() const
|
|
|
+{
|
|
|
+ return _tangentBinormalId.size();
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::isGenerateTangentBinormalId(const std::string& id) const
|
|
|
+{
|
|
|
+ return _tangentBinormalId.find(id) != _tangentBinormalId.end();
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::normalMapGeneration() const
|
|
|
+{
|
|
|
+ return _normalMap;
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::getHeightmapResolution(int* x, int* y) const
|
|
|
+{
|
|
|
+ *x = _heightmapResolution[0];
|
|
|
+ *y = _heightmapResolution[1];
|
|
|
+}
|
|
|
+
|
|
|
+const Vector3& EncoderArguments::getHeightmapWorldSize() const
|
|
|
+{
|
|
|
+ return _heightmapWorldSize;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::parseErrorOccured() const
|
|
|
+{
|
|
|
+ return _parseError;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::fileExists() const
|
|
|
+{
|
|
|
+ if (_filePath.length() > 0)
|
|
|
+ {
|
|
|
+ struct stat buf;
|
|
|
+ if (stat(_filePath.c_str(), &buf) != -1)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void splitString(const char* str, std::vector<std::string>* tokens)
|
|
|
+{
|
|
|
+ // Split node id list into tokens
|
|
|
+ unsigned int length = strlen(str);
|
|
|
+ char* temp = new char[length + 1];
|
|
|
+ strcpy(temp, str);
|
|
|
+ char* tok = strtok(temp, ",");
|
|
|
+ while (tok)
|
|
|
+ {
|
|
|
+ tokens->push_back(tok);
|
|
|
+ tok = strtok(NULL, ",");
|
|
|
+ }
|
|
|
+ delete[] temp;
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::printUsage() const
|
|
|
+{
|
|
|
+ LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n" \
|
|
|
+ "Encoder version: " ENCODER_VERSION "\n\n" \
|
|
|
+ "Supported file extensions:\n" \
|
|
|
+ " .fbx\t(FBX scenes)\n" \
|
|
|
+ " .ttf\t(TrueType fonts)\n" \
|
|
|
+ "\n" \
|
|
|
+ "General options:\n" \
|
|
|
+ " -v <verbosity>\tVerbosity level (0-4).\n" \
|
|
|
+ "\n" \
|
|
|
+ "FBX file options:\n" \
|
|
|
+ " -i <id>\tFilter by node ID.\n" \
|
|
|
+ " -t\t\tWrite text/xml.\n" \
|
|
|
+ " -g:auto\tAutomatically group animation channels into a new animation.\n" \
|
|
|
+ " -g:none\tDo not prompt to group animations.\n" \
|
|
|
+ " -g <node id> <animation id>\n" \
|
|
|
+ "\t\tGroup all animation channels targeting the nodes into a \n" \
|
|
|
+ "\t\tnew animation.\n" \
|
|
|
+ " -m\t\tOutput material file for scene.\n" \
|
|
|
+ " -tb <node id>\n" \
|
|
|
+ "\t\tGenerates tangents and binormals for the given node.\n" \
|
|
|
+ " -oa\n" \
|
|
|
+ "\t\tOptimizes animations by analyzing animation channel data and\n" \
|
|
|
+ "\t\tremoving any channels that contain default/identity values\n" \
|
|
|
+ "\t\tand removing any duplicate contiguous keyframes, which are \n" \
|
|
|
+ "\t\tcommon when exporting baked animation data.\n" \
|
|
|
+ " -h <size> \"<node ids>\" <filename>\n" \
|
|
|
+ "\t\tGenerates a single heightmap image using meshes from the \n" \
|
|
|
+ "\t\tspecified nodes. \n" \
|
|
|
+ "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
|
|
|
+ "\t\tindicating the dimensions of the produced heightmap image.\n" \
|
|
|
+ "\t\t<node ids> should be in quotes with a space between each id.\n" \
|
|
|
+ "\t\tFilename is the name of the image (PNG) to be saved.\n" \
|
|
|
+ "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
|
|
|
+ "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
|
|
|
+ "\n" \
|
|
|
+ "Normal map options:\n" \
|
|
|
+ " -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
|
|
|
+ " -s\t\tSize/resolution of the input heightmap image (required for RAW files)\n" \
|
|
|
+ " -w <size>\tSpecifies the size of an input terrain heightmap file in world\n" \
|
|
|
+ "\t\tunits, along the X, Y and Z axes. <size> should be three \n" \
|
|
|
+ "\t\tcomma-separated numbers in the format \"X,Y,Z\". \n" \
|
|
|
+ "\t\tThe Y value represents the maximum possible height value of a \n" \
|
|
|
+ "\t\tfull intensity heightmap pixel.\n" \
|
|
|
+ "\n" \
|
|
|
+ " \t\tNormal map generation can be used to create object-space normal maps from \n" \
|
|
|
+ " \t\theightmap images. Heightmaps must be in either PNG format (where the \n" \
|
|
|
+ " \t\tintensity of each pixel represents a height value), or in RAW format \n" \
|
|
|
+ " \t\t(8 or 16-bit), which is a common headerless format supported by most \n" \
|
|
|
+ " \t\tterrain generation tools.\n" \
|
|
|
+ "\n" \
|
|
|
+ "TTF file options:\n" \
|
|
|
+ " -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
|
|
|
+ " -p\t\tOutput font preview.\n" \
|
|
|
+ " -f\t\tFormat of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
|
|
|
+ "\n");
|
|
|
+ exit(8);
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::fontPreviewEnabled() const
|
|
|
+{
|
|
|
+ return _fontPreview;
|
|
|
+}
|
|
|
+
|
|
|
+Font::FontFormat EncoderArguments::getFontFormat() const
|
|
|
+{
|
|
|
+ return _fontFormat;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::textOutputEnabled() const
|
|
|
+{
|
|
|
+ return _textOutput;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::optimizeAnimationsEnabled() const
|
|
|
+{
|
|
|
+ return _optimizeAnimations;
|
|
|
+}
|
|
|
+
|
|
|
+bool EncoderArguments::outputMaterialEnabled() const
|
|
|
+{
|
|
|
+ return _outputMaterial;
|
|
|
+}
|
|
|
+
|
|
|
+const char* EncoderArguments::getNodeId() const
|
|
|
+{
|
|
|
+ if (_nodeId.length() == 0)
|
|
|
+ {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ return _nodeId.c_str();
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<unsigned int> EncoderArguments::getFontSizes() const
|
|
|
+{
|
|
|
+ return _fontSizes;
|
|
|
+}
|
|
|
+
|
|
|
+EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
|
|
|
+{
|
|
|
+ if (_filePath.length() < 5)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_UNKNOWN;
|
|
|
+ }
|
|
|
+ // Extract the extension
|
|
|
+ std::string ext = "";
|
|
|
+ size_t pos = _filePath.find_last_of(".");
|
|
|
+ if (pos != std::string::npos)
|
|
|
+ {
|
|
|
+ ext = _filePath.substr(pos + 1);
|
|
|
+ }
|
|
|
+ for (size_t i = 0; i < ext.size(); ++i)
|
|
|
+ ext[i] = (char)tolower(ext[i]);
|
|
|
+
|
|
|
+ // Match every supported extension with its format constant
|
|
|
+ if (ext.compare("dae") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_DAE;
|
|
|
+ }
|
|
|
+ if (ext.compare("fbx") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_FBX;
|
|
|
+ }
|
|
|
+ if (ext.compare("ttf") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_TTF;
|
|
|
+ }
|
|
|
+ if (ext.compare("gpb") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_GPB;
|
|
|
+ }
|
|
|
+ if (ext.compare("png") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_PNG;
|
|
|
+ }
|
|
|
+ if (ext.compare("raw") == 0)
|
|
|
+ {
|
|
|
+ return FILEFORMAT_RAW;
|
|
|
+ }
|
|
|
+
|
|
|
+ return FILEFORMAT_UNKNOWN;
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
|
|
|
+{
|
|
|
+ const std::string& str = options[*index];
|
|
|
+ if (str.length() == 0 && str[0] != '-')
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch (str[1])
|
|
|
+ {
|
|
|
+ case 'f':
|
|
|
+ if (str.compare("-f:b") == 0)
|
|
|
+ {
|
|
|
+ _fontFormat = Font::BITMAP;
|
|
|
+ }
|
|
|
+ else if (str.compare("-f:d") == 0)
|
|
|
+ {
|
|
|
+ _fontFormat = Font::DISTANCE_FIELD;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'g':
|
|
|
+ if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
|
|
|
+ {
|
|
|
+ _animationGrouping = ANIMATIONGROUP_AUTO;
|
|
|
+ }
|
|
|
+ else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
|
|
|
+ {
|
|
|
+ _animationGrouping = ANIMATIONGROUP_OFF;
|
|
|
+ }
|
|
|
+ else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
|
|
|
+ {
|
|
|
+ // read two strings, make sure not to go out of bounds
|
|
|
+ if ((*index + 2) >= options.size())
|
|
|
+ {
|
|
|
+ LOG(1, "Error: -g requires 2 arguments.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ (*index)++;
|
|
|
+ _groupAnimationNodeId.push_back(options[*index]);
|
|
|
+ (*index)++;
|
|
|
+ _groupAnimationAnimationId.push_back(options[*index]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'i':
|
|
|
+ // Node ID
|
|
|
+ (*index)++;
|
|
|
+ if (*index < options.size())
|
|
|
+ {
|
|
|
+ _nodeId.assign(options[*index]);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'o':
|
|
|
+ // Optimization flag
|
|
|
+ if (str == "-oa")
|
|
|
+ {
|
|
|
+ // Optimize animations
|
|
|
+ _optimizeAnimations = true;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'h':
|
|
|
+ {
|
|
|
+ bool isHighPrecision = str.compare("-hp") == 0;
|
|
|
+ if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
|
|
|
+ {
|
|
|
+ (*index)++;
|
|
|
+ if (*index < (options.size() + 2))
|
|
|
+ {
|
|
|
+ _heightmaps.resize(_heightmaps.size() + 1);
|
|
|
+ HeightmapOption& heightmap = _heightmaps.back();
|
|
|
+
|
|
|
+ heightmap.isHighPrecision = isHighPrecision;
|
|
|
+
|
|
|
+ // Read heightmap size
|
|
|
+ std::vector<std::string> parts;
|
|
|
+ splitString(options[*index].c_str(), &parts);
|
|
|
+ if (parts.size() != 2)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ heightmap.width = atoi(parts[0].c_str());
|
|
|
+ heightmap.height = atoi(parts[1].c_str());
|
|
|
+
|
|
|
+ // Put some artificial bounds on heightmap dimensions
|
|
|
+ if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: size argument for -h|-heightmap must be between (1,1) and (%d,%d).\n", (int)HEIGHTMAP_SIZE_MAX, (int)HEIGHTMAP_SIZE_MAX);
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Split node id list into tokens
|
|
|
+ (*index)++;
|
|
|
+ splitString(options[*index].c_str(), &heightmap.nodeIds);
|
|
|
+
|
|
|
+ // Store output filename
|
|
|
+ (*index)++;
|
|
|
+ heightmap.filename = options[*index];
|
|
|
+ if (heightmap.filename.empty())
|
|
|
+ {
|
|
|
+ LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ensure the output filename has a .png extention
|
|
|
+ if (heightmap.filename.length() > 5)
|
|
|
+ {
|
|
|
+ const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
|
|
|
+ if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
|
|
|
+ heightmap.filename += ".png";
|
|
|
+ }
|
|
|
+ else
|
|
|
+ heightmap.filename += ".png";
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ LOG(1, "Error: missing argument for -h|-heightmap.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'm':
|
|
|
+ if (str.compare("-m") == 0)
|
|
|
+ {
|
|
|
+ // generate a material file
|
|
|
+ _outputMaterial = true;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'n':
|
|
|
+ _normalMap = true;
|
|
|
+ break;
|
|
|
+ case 'w':
|
|
|
+ {
|
|
|
+ // Read world size
|
|
|
+ (*index)++;
|
|
|
+ if (*index >= options.size())
|
|
|
+ {
|
|
|
+ LOG(1, "Error: missing world size argument for -w.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ std::vector<std::string> parts;
|
|
|
+ splitString(options[*index].c_str(), &parts);
|
|
|
+ if (parts.size() != 3)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid world size argument for -w.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ _heightmapWorldSize.x = (float)atof(parts[0].c_str());
|
|
|
+ _heightmapWorldSize.y = (float)atof(parts[1].c_str());
|
|
|
+ _heightmapWorldSize.z = (float)atof(parts[2].c_str());
|
|
|
+ if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid world size argument for -w.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'p':
|
|
|
+ _fontPreview = true;
|
|
|
+ break;
|
|
|
+ case 's':
|
|
|
+ if (_normalMap)
|
|
|
+ {
|
|
|
+ (*index)++;
|
|
|
+ if (*index >= options.size())
|
|
|
+ {
|
|
|
+ LOG(1, "Error: missing argument for -s.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // Heightmap size
|
|
|
+ std::vector<std::string> parts;
|
|
|
+ splitString(options[*index].c_str(), &parts);
|
|
|
+ if (parts.size() != 2 ||
|
|
|
+ (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
|
|
|
+ (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid argument for -s.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Font Sizes
|
|
|
+ // old format was -s##
|
|
|
+ const char* sizes = NULL;
|
|
|
+ if (str.length() > 2)
|
|
|
+ {
|
|
|
+ char n = str[2];
|
|
|
+ if (n > '0' && n <= '9')
|
|
|
+ {
|
|
|
+ sizes = str.c_str() + 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ (*index)++;
|
|
|
+ if (*index < options.size())
|
|
|
+ {
|
|
|
+ sizes = options[*index].c_str();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sizes == NULL)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid format for argument: -s");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Parse comma-separated list of font sizes
|
|
|
+ char* ptr = const_cast<char*>(sizes);
|
|
|
+ std::string sizeStr;
|
|
|
+ while (ptr)
|
|
|
+ {
|
|
|
+ char* end = strchr(ptr, ',');
|
|
|
+ if (end)
|
|
|
+ {
|
|
|
+ sizeStr = std::string(ptr, end - ptr);
|
|
|
+ ptr = end + 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sizeStr = ptr;
|
|
|
+ ptr = NULL;
|
|
|
+ }
|
|
|
+ if (sizeStr.length() > 0)
|
|
|
+ {
|
|
|
+ int size = atoi(sizeStr.c_str());
|
|
|
+ if (size <= 0)
|
|
|
+ {
|
|
|
+ LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ _fontSizes.push_back((unsigned int)size);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 't':
|
|
|
+ if (str.compare("-t") == 0)
|
|
|
+ {
|
|
|
+ _textOutput = true;
|
|
|
+ }
|
|
|
+ else if (str.compare("-tb") == 0)
|
|
|
+ {
|
|
|
+ if ((*index + 1) >= options.size())
|
|
|
+ {
|
|
|
+ LOG(1, "Error: -tb requires 1 argument.\n");
|
|
|
+ _parseError = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ (*index)++;
|
|
|
+ std::string nodeId = options[*index];
|
|
|
+ if (nodeId.length() > 0)
|
|
|
+ {
|
|
|
+ _tangentBinormalId.insert(nodeId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'v':
|
|
|
+ (*index)++;
|
|
|
+ if (*index < options.size())
|
|
|
+ {
|
|
|
+ __logVerbosity = atoi(options[*index].c_str());
|
|
|
+ if (__logVerbosity < 0)
|
|
|
+ __logVerbosity = 0;
|
|
|
+ else if (__logVerbosity > 4)
|
|
|
+ __logVerbosity = 4;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::setInputfilePath(const std::string& inputPath)
|
|
|
+{
|
|
|
+ _filePath.assign(getRealPath(inputPath));
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::setOutputfilePath(const std::string& outputPath)
|
|
|
+{
|
|
|
+ std::string ext = getOutputFileExtension();
|
|
|
+
|
|
|
+ if (outputPath.size() > 0 && outputPath[0] != '\0')
|
|
|
+ {
|
|
|
+ std::string realPath = getRealPath(outputPath);
|
|
|
+ if (endsWith(realPath.c_str(), ext.c_str()))
|
|
|
+ {
|
|
|
+ _fileOutputPath.assign(realPath);
|
|
|
+ }
|
|
|
+ else if (endsWith(outputPath.c_str(), "/"))
|
|
|
+ {
|
|
|
+ std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));
|
|
|
+
|
|
|
+ _fileOutputPath.assign(outputPath);
|
|
|
+ _fileOutputPath.append(filenameNoExt);
|
|
|
+ _fileOutputPath.append(ext);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
|
|
|
+ int pos = realPath.find_last_of("/");
|
|
|
+ if (pos)
|
|
|
+ {
|
|
|
+ _fileOutputPath = realPath.substr(0, pos);
|
|
|
+ _fileOutputPath.append("/");
|
|
|
+ _fileOutputPath.append(filenameNoExt);
|
|
|
+ _fileOutputPath.append(ext);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::string EncoderArguments::getRealPath(const std::string& filepath)
|
|
|
+{
|
|
|
+ char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
|
|
|
+ realpath(filepath.c_str(), path);
|
|
|
+ replace_char(path, '\\', '/');
|
|
|
+ return std::string(path);
|
|
|
+}
|
|
|
+
|
|
|
+void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
|
|
|
+{
|
|
|
+ for (; *str != '\0'; ++str)
|
|
|
+ {
|
|
|
+ if (*str == oldChar)
|
|
|
+ {
|
|
|
+ *str = newChar;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::string concat(const std::string& a, const char* b)
|
|
|
+{
|
|
|
+ std::string str(a);
|
|
|
+ str.append(b);
|
|
|
+ return str;
|
|
|
+}
|
|
|
+
|
|
|
+void unittestsEncoderArguments()
|
|
|
+{
|
|
|
+ std::string dir = EncoderArguments::getRealPath(".");
|
|
|
+ std::string exePath = EncoderArguments::getRealPath(".");
|
|
|
+ exePath.append("/gameplay-encoder.exe");
|
|
|
+ const char* exe = exePath.c_str();
|
|
|
+ {
|
|
|
+ const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getAnimationId("root"), ("movements")));
|
|
|
+ assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
|
|
|
+ assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test with only input file name (relative)
|
|
|
+ const char* argv[] = {exe, "input.fbx"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
|
|
|
+ equals(args.getOutputDirPath(), dir);
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test specifying a relative output path
|
|
|
+ const char* argv[] = {exe, "input.fbx", "output.gpb"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test specifying a relative output path
|
|
|
+ const char* argv[] = {exe, "input.fbx", "output.gpb"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test specifying a relative output path to a directory
|
|
|
+ const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test parsing some arguments
|
|
|
+ const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
+ assert(args.textOutputEnabled());
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test output file with no file extension
|
|
|
+ const char* argv[] = {exe, "input.fbx", "output"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
+ }
|
|
|
+ {
|
|
|
+ // Test output file with wrong file extension
|
|
|
+ const char* argv[] = {exe, "input.fbx", "output.fbx"};
|
|
|
+ EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
|
|
|
+ assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
|
|
|
+ assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+}
|