EncoderArguments.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. #include "Base.h"
  2. #include "EncoderArguments.h"
  3. #include "StringUtil.h"
  4. #ifdef WIN32
  5. #define PATH_MAX _MAX_PATH
  6. #define realpath(A,B) _fullpath(B,A,PATH_MAX)
  7. #endif
  8. // The encoder version number should be incremented when a feature is added to the encoder.
  9. // The encoder version is not the same as the GPB version.
  10. #define ENCODER_VERSION "3.0.0"
  11. #define HEIGHTMAP_SIZE_MAX 2049
  12. namespace gameplay
  13. {
  14. static EncoderArguments* __instance;
  15. extern int __logVerbosity = 1;
  16. EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
  17. _normalMap(false),
  18. _parseError(false),
  19. _fontPreview(false),
  20. _fontFormat(Font::BITMAP),
  21. _textOutput(false),
  22. _optimizeAnimations(false),
  23. _animationGrouping(ANIMATIONGROUP_PROMPT),
  24. _outputMaterial(false),
  25. _generateTextureGutter(false)
  26. {
  27. __instance = this;
  28. memset(_heightmapResolution, 0, sizeof(int) * 2);
  29. if (argc > 1)
  30. {
  31. // read the options
  32. std::vector<std::string> arguments;
  33. for (size_t i = 1; i < argc; ++i)
  34. {
  35. arguments.push_back(argv[i]);
  36. }
  37. size_t index = 0;
  38. for (size_t i = 0; i < arguments.size(); ++i)
  39. {
  40. if (arguments[i][0] == '-')
  41. {
  42. readOption(arguments, &i);
  43. index = i + 1;
  44. }
  45. }
  46. if (arguments.size() - index == 2)
  47. {
  48. setInputfilePath(arguments[index]);
  49. setOutputfilePath(arguments[index + 1]);
  50. }
  51. else if (arguments.size() - index == 1)
  52. {
  53. setInputfilePath(arguments[index]);
  54. }
  55. }
  56. else
  57. {
  58. _parseError = true;
  59. }
  60. }
  61. EncoderArguments::~EncoderArguments(void)
  62. {
  63. }
  64. EncoderArguments* EncoderArguments::getInstance()
  65. {
  66. return __instance;
  67. }
  68. const std::string& EncoderArguments::getFilePath() const
  69. {
  70. return _filePath;
  71. }
  72. const std::string EncoderArguments::getFileDirPath() const
  73. {
  74. int pos = _filePath.find_last_of('/');
  75. return (pos == -1 ? _filePath : _filePath.substr(0, pos));
  76. }
  77. const char* EncoderArguments::getFilePathPointer() const
  78. {
  79. return _filePath.c_str();
  80. }
  81. const std::string EncoderArguments::getFileName() const
  82. {
  83. int pos = _filePath.find_last_of('/');
  84. return (pos == -1 ? _filePath : _filePath.substr(pos + 1));
  85. }
  86. std::string EncoderArguments::getOutputDirPath() const
  87. {
  88. if (_fileOutputPath.size() > 0)
  89. {
  90. int pos = _fileOutputPath.find_last_of('/');
  91. return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
  92. }
  93. else
  94. {
  95. int pos = _filePath.find_last_of('/');
  96. return (pos == -1 ? _filePath : _filePath.substr(0, pos));
  97. }
  98. }
  99. std::string EncoderArguments::getOutputFileExtension() const
  100. {
  101. switch (getFileFormat())
  102. {
  103. case FILEFORMAT_TMX:
  104. return ".scene";
  105. case FILEFORMAT_PNG:
  106. case FILEFORMAT_RAW:
  107. if (_normalMap)
  108. return ".png";
  109. default:
  110. return ".gpb";
  111. }
  112. }
  113. std::string EncoderArguments::getOutputFilePath() const
  114. {
  115. if (_fileOutputPath.size() > 0)
  116. {
  117. // Output file explicitly set
  118. return _fileOutputPath;
  119. }
  120. else
  121. {
  122. // Generate an output file path
  123. int pos = _filePath.find_last_of('.');
  124. std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);
  125. // Modify the original file name if the output extension can be the same as the input
  126. if (_normalMap)
  127. {
  128. outputFilePath.append("_normalmap");
  129. }
  130. outputFilePath.append(getOutputFileExtension());
  131. return outputFilePath;
  132. }
  133. }
  134. const std::vector<std::string>& EncoderArguments::getGroupAnimationNodeId() const
  135. {
  136. return _groupAnimationNodeId;
  137. }
  138. const std::vector<std::string>& EncoderArguments::getGroupAnimationAnimationId() const
  139. {
  140. return _groupAnimationAnimationId;
  141. }
  142. bool EncoderArguments::containsGroupNodeId(const std::string& nodeId) const
  143. {
  144. return find(_groupAnimationNodeId.begin(), _groupAnimationNodeId.end(), nodeId) != _groupAnimationNodeId.end();
  145. }
  146. const std::string EncoderArguments::getAnimationId(const std::string& nodeId) const
  147. {
  148. for (size_t i = 0, size = _groupAnimationNodeId.size(); i < size; ++i)
  149. {
  150. if (_groupAnimationNodeId[i].compare(nodeId) == 0)
  151. {
  152. return _groupAnimationAnimationId[i];
  153. }
  154. }
  155. return "";
  156. }
  157. EncoderArguments::AnimationGroupOption EncoderArguments::getAnimationGrouping() const
  158. {
  159. return _animationGrouping;
  160. }
  161. const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeightmapOptions() const
  162. {
  163. return _heightmaps;
  164. }
  165. unsigned int EncoderArguments::tangentBinormalIdCount() const
  166. {
  167. return _tangentBinormalId.size();
  168. }
  169. bool EncoderArguments::isGenerateTangentBinormalId(const std::string& id) const
  170. {
  171. return _tangentBinormalId.find(id) != _tangentBinormalId.end();
  172. }
  173. bool EncoderArguments::normalMapGeneration() const
  174. {
  175. return _normalMap;
  176. }
  177. void EncoderArguments::getHeightmapResolution(int* x, int* y) const
  178. {
  179. *x = _heightmapResolution[0];
  180. *y = _heightmapResolution[1];
  181. }
  182. const Vector3& EncoderArguments::getHeightmapWorldSize() const
  183. {
  184. return _heightmapWorldSize;
  185. }
  186. bool EncoderArguments::parseErrorOccured() const
  187. {
  188. return _parseError;
  189. }
  190. bool EncoderArguments::fileExists() const
  191. {
  192. if (_filePath.length() > 0)
  193. {
  194. struct stat buf;
  195. if (stat(_filePath.c_str(), &buf) != -1)
  196. {
  197. return true;
  198. }
  199. }
  200. return false;
  201. }
  202. void splitString(const char* str, std::vector<std::string>* tokens)
  203. {
  204. // Split node id list into tokens
  205. unsigned int length = strlen(str);
  206. char* temp = new char[length + 1];
  207. strcpy(temp, str);
  208. char* tok = strtok(temp, ",");
  209. while (tok)
  210. {
  211. tokens->push_back(tok);
  212. tok = strtok(NULL, ",");
  213. }
  214. delete[] temp;
  215. }
  216. void EncoderArguments::printUsage() const
  217. {
  218. LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n" \
  219. "Encoder version: " ENCODER_VERSION "\n\n" \
  220. "Supported file extensions:\n" \
  221. " .fbx\t(FBX scenes)\n" \
  222. " .ttf\t(TrueType fonts)\n" \
  223. "\n" \
  224. "General options:\n" \
  225. " -v <verbosity>\tVerbosity level (0-4).\n" \
  226. "\n" \
  227. "FBX file options:\n" \
  228. " -i <id>\tFilter by node ID.\n" \
  229. " -t\t\tWrite text/xml.\n" \
  230. " -g:auto\tAutomatically group animation channels into a new animation.\n" \
  231. " -g:none\tDo not prompt to group animations.\n" \
  232. " -g <node id> <animation id>\n" \
  233. "\t\tGroup all animation channels targeting the nodes into a \n" \
  234. "\t\tnew animation.\n" \
  235. " -m\t\tOutput material file for scene.\n" \
  236. " -tb <node id>\n" \
  237. "\t\tGenerates tangents and binormals for the given node.\n" \
  238. " -oa\n" \
  239. "\t\tOptimizes animations by analyzing animation channel data and\n" \
  240. "\t\tremoving any channels that contain default/identity values\n" \
  241. "\t\tand removing any duplicate contiguous keyframes, which are \n" \
  242. "\t\tcommon when exporting baked animation data.\n" \
  243. " -h <size> \"<node ids>\" <filename>\n" \
  244. "\t\tGenerates a single heightmap image using meshes from the \n" \
  245. "\t\tspecified nodes. \n" \
  246. "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
  247. "\t\tindicating the dimensions of the produced heightmap image.\n" \
  248. "\t\t<node ids> should be in quotes with a space between each id.\n" \
  249. "\t\tFilename is the name of the image (PNG) to be saved.\n" \
  250. "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
  251. "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
  252. "\n" \
  253. "TMX file options:\n" \
  254. " -tg\tEnable texture gutter's around tiles. This will modify any referenced\n" \
  255. " \ttile sets to add a 1px border around it to prevent seams.\n"
  256. " -tg:none\tDo not priduce a texture gutter.\n"
  257. "\n" \
  258. "Normal map options:\n" \
  259. " -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
  260. " -s\t\tSize/resolution of the input heightmap image (required for RAW files)\n" \
  261. " -w <size>\tSpecifies the size of an input terrain heightmap file in world\n" \
  262. "\t\tunits, along the X, Y and Z axes. <size> should be three \n" \
  263. "\t\tcomma-separated numbers in the format \"X,Y,Z\". \n" \
  264. "\t\tThe Y value represents the maximum possible height value of a \n" \
  265. "\t\tfull intensity heightmap pixel.\n" \
  266. "\n" \
  267. " \t\tNormal map generation can be used to create object-space normal maps from \n" \
  268. " \t\theightmap images. Heightmaps must be in either PNG format (where the \n" \
  269. " \t\tintensity of each pixel represents a height value), or in RAW format \n" \
  270. " \t\t(8 or 16-bit), which is a common headerless format supported by most \n" \
  271. " \t\tterrain generation tools.\n" \
  272. "\n" \
  273. "TTF file options:\n" \
  274. " -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
  275. " -p\t\tOutput font preview.\n" \
  276. " -f\t\tFormat of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
  277. "\n");
  278. exit(8);
  279. }
  280. bool EncoderArguments::fontPreviewEnabled() const
  281. {
  282. return _fontPreview;
  283. }
  284. Font::FontFormat EncoderArguments::getFontFormat() const
  285. {
  286. return _fontFormat;
  287. }
  288. bool EncoderArguments::textOutputEnabled() const
  289. {
  290. return _textOutput;
  291. }
  292. bool EncoderArguments::optimizeAnimationsEnabled() const
  293. {
  294. return _optimizeAnimations;
  295. }
  296. bool EncoderArguments::outputMaterialEnabled() const
  297. {
  298. return _outputMaterial;
  299. }
  300. bool EncoderArguments::generateTextureGutter() const
  301. {
  302. return _generateTextureGutter;
  303. }
  304. const char* EncoderArguments::getNodeId() const
  305. {
  306. if (_nodeId.length() == 0)
  307. {
  308. return NULL;
  309. }
  310. return _nodeId.c_str();
  311. }
  312. std::vector<unsigned int> EncoderArguments::getFontSizes() const
  313. {
  314. return _fontSizes;
  315. }
  316. EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
  317. {
  318. if (_filePath.length() < 5)
  319. {
  320. return FILEFORMAT_UNKNOWN;
  321. }
  322. // Extract the extension
  323. std::string ext = "";
  324. size_t pos = _filePath.find_last_of(".");
  325. if (pos != std::string::npos)
  326. {
  327. ext = _filePath.substr(pos + 1);
  328. }
  329. for (size_t i = 0; i < ext.size(); ++i)
  330. ext[i] = (char)tolower(ext[i]);
  331. // Match every supported extension with its format constant
  332. if (ext.compare("fbx") == 0)
  333. {
  334. return FILEFORMAT_FBX;
  335. }
  336. if (ext.compare("tmx") == 0)
  337. {
  338. return FILEFORMAT_TMX;
  339. }
  340. if (ext.compare("ttf") == 0)
  341. {
  342. return FILEFORMAT_TTF;
  343. }
  344. if (ext.compare("otf") == 0)
  345. {
  346. return FILEFORMAT_TTF;
  347. }
  348. if (ext.compare("gpb") == 0)
  349. {
  350. return FILEFORMAT_GPB;
  351. }
  352. if (ext.compare("png") == 0)
  353. {
  354. return FILEFORMAT_PNG;
  355. }
  356. if (ext.compare("raw") == 0)
  357. {
  358. return FILEFORMAT_RAW;
  359. }
  360. return FILEFORMAT_UNKNOWN;
  361. }
  362. void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
  363. {
  364. const std::string& str = options[*index];
  365. if (str.length() == 0 && str[0] != '-')
  366. {
  367. return;
  368. }
  369. switch (str[1])
  370. {
  371. case 'f':
  372. if (str.compare("-f:b") == 0)
  373. {
  374. _fontFormat = Font::BITMAP;
  375. }
  376. else if (str.compare("-f:d") == 0)
  377. {
  378. _fontFormat = Font::DISTANCE_FIELD;
  379. }
  380. break;
  381. case 'g':
  382. if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
  383. {
  384. _animationGrouping = ANIMATIONGROUP_AUTO;
  385. }
  386. else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
  387. {
  388. _animationGrouping = ANIMATIONGROUP_OFF;
  389. }
  390. else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
  391. {
  392. // read two strings, make sure not to go out of bounds
  393. if ((*index + 2) >= options.size())
  394. {
  395. LOG(1, "Error: -g requires 2 arguments.\n");
  396. _parseError = true;
  397. return;
  398. }
  399. (*index)++;
  400. _groupAnimationNodeId.push_back(options[*index]);
  401. (*index)++;
  402. _groupAnimationAnimationId.push_back(options[*index]);
  403. }
  404. break;
  405. case 'i':
  406. // Node ID
  407. (*index)++;
  408. if (*index < options.size())
  409. {
  410. _nodeId.assign(options[*index]);
  411. }
  412. else
  413. {
  414. LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
  415. _parseError = true;
  416. return;
  417. }
  418. break;
  419. case 'o':
  420. // Optimization flag
  421. if (str == "-oa")
  422. {
  423. // Optimize animations
  424. _optimizeAnimations = true;
  425. }
  426. break;
  427. case 'h':
  428. {
  429. bool isHighPrecision = str.compare("-hp") == 0;
  430. if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
  431. {
  432. (*index)++;
  433. if (*index < (options.size() + 2))
  434. {
  435. _heightmaps.resize(_heightmaps.size() + 1);
  436. HeightmapOption& heightmap = _heightmaps.back();
  437. heightmap.isHighPrecision = isHighPrecision;
  438. // Read heightmap size
  439. std::vector<std::string> parts;
  440. splitString(options[*index].c_str(), &parts);
  441. if (parts.size() != 2)
  442. {
  443. LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
  444. _parseError = true;
  445. return;
  446. }
  447. heightmap.width = atoi(parts[0].c_str());
  448. heightmap.height = atoi(parts[1].c_str());
  449. // Put some artificial bounds on heightmap dimensions
  450. if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
  451. {
  452. 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);
  453. _parseError = true;
  454. return;
  455. }
  456. // Split node id list into tokens
  457. (*index)++;
  458. splitString(options[*index].c_str(), &heightmap.nodeIds);
  459. // Store output filename
  460. (*index)++;
  461. heightmap.filename = options[*index];
  462. if (heightmap.filename.empty())
  463. {
  464. LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
  465. _parseError = true;
  466. return;
  467. }
  468. // Ensure the output filename has a .png extention
  469. if (heightmap.filename.length() > 5)
  470. {
  471. const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
  472. if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
  473. heightmap.filename += ".png";
  474. }
  475. else
  476. heightmap.filename += ".png";
  477. }
  478. else
  479. {
  480. LOG(1, "Error: missing argument for -h|-heightmap.\n");
  481. _parseError = true;
  482. return;
  483. }
  484. }
  485. }
  486. break;
  487. case 'm':
  488. if (str.compare("-m") == 0)
  489. {
  490. // generate a material file
  491. _outputMaterial = true;
  492. }
  493. break;
  494. case 'n':
  495. _normalMap = true;
  496. break;
  497. case 'w':
  498. {
  499. // Read world size
  500. (*index)++;
  501. if (*index >= options.size())
  502. {
  503. LOG(1, "Error: missing world size argument for -w.\n");
  504. _parseError = true;
  505. return;
  506. }
  507. std::vector<std::string> parts;
  508. splitString(options[*index].c_str(), &parts);
  509. if (parts.size() != 3)
  510. {
  511. LOG(1, "Error: invalid world size argument for -w.\n");
  512. _parseError = true;
  513. return;
  514. }
  515. _heightmapWorldSize.x = (float)atof(parts[0].c_str());
  516. _heightmapWorldSize.y = (float)atof(parts[1].c_str());
  517. _heightmapWorldSize.z = (float)atof(parts[2].c_str());
  518. if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
  519. {
  520. LOG(1, "Error: invalid world size argument for -w.\n");
  521. _parseError = true;
  522. return;
  523. }
  524. }
  525. break;
  526. case 'p':
  527. _fontPreview = true;
  528. break;
  529. case 's':
  530. if (_normalMap)
  531. {
  532. (*index)++;
  533. if (*index >= options.size())
  534. {
  535. LOG(1, "Error: missing argument for -s.\n");
  536. _parseError = true;
  537. return;
  538. }
  539. // Heightmap size
  540. std::vector<std::string> parts;
  541. splitString(options[*index].c_str(), &parts);
  542. if (parts.size() != 2 ||
  543. (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
  544. (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
  545. {
  546. LOG(1, "Error: invalid argument for -s.\n");
  547. _parseError = true;
  548. return;
  549. }
  550. }
  551. else
  552. {
  553. // Font Sizes
  554. // old format was -s##
  555. const char* sizes = NULL;
  556. if (str.length() > 2)
  557. {
  558. char n = str[2];
  559. if (n > '0' && n <= '9')
  560. {
  561. sizes = str.c_str() + 2;
  562. }
  563. }
  564. else
  565. {
  566. (*index)++;
  567. if (*index < options.size())
  568. {
  569. sizes = options[*index].c_str();
  570. }
  571. }
  572. if (sizes == NULL)
  573. {
  574. LOG(1, "Error: invalid format for argument: -s");
  575. _parseError = true;
  576. return;
  577. }
  578. // Parse comma-separated list of font sizes
  579. char* ptr = const_cast<char*>(sizes);
  580. std::string sizeStr;
  581. while (ptr)
  582. {
  583. char* end = strchr(ptr, ',');
  584. if (end)
  585. {
  586. sizeStr = std::string(ptr, end - ptr);
  587. ptr = end + 1;
  588. }
  589. else
  590. {
  591. sizeStr = ptr;
  592. ptr = NULL;
  593. }
  594. if (sizeStr.length() > 0)
  595. {
  596. int size = atoi(sizeStr.c_str());
  597. if (size <= 0)
  598. {
  599. LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
  600. _parseError = true;
  601. return;
  602. }
  603. _fontSizes.push_back((unsigned int)size);
  604. }
  605. }
  606. }
  607. break;
  608. case 't':
  609. if (str.compare("-t") == 0)
  610. {
  611. _textOutput = true;
  612. }
  613. else if (str.compare("-tb") == 0)
  614. {
  615. if ((*index + 1) >= options.size())
  616. {
  617. LOG(1, "Error: -tb requires 1 argument.\n");
  618. _parseError = true;
  619. return;
  620. }
  621. (*index)++;
  622. std::string nodeId = options[*index];
  623. if (nodeId.length() > 0)
  624. {
  625. _tangentBinormalId.insert(nodeId);
  626. }
  627. }
  628. else if (str.compare("-textureGutter:none") == 0 || str.compare("-tg:none") == 0)
  629. {
  630. _generateTextureGutter = false;
  631. }
  632. else if (str.compare("-textureGutter") == 0 || str.compare("-tg") == 0)
  633. {
  634. _generateTextureGutter = true;
  635. }
  636. break;
  637. case 'v':
  638. (*index)++;
  639. if (*index < options.size())
  640. {
  641. __logVerbosity = atoi(options[*index].c_str());
  642. if (__logVerbosity < 0)
  643. __logVerbosity = 0;
  644. else if (__logVerbosity > 4)
  645. __logVerbosity = 4;
  646. }
  647. break;
  648. default:
  649. break;
  650. }
  651. }
  652. void EncoderArguments::setInputfilePath(const std::string& inputPath)
  653. {
  654. _filePath.assign(getRealPath(inputPath));
  655. }
  656. void EncoderArguments::setOutputfilePath(const std::string& outputPath)
  657. {
  658. std::string ext = getOutputFileExtension();
  659. if (outputPath.size() > 0 && outputPath[0] != '\0')
  660. {
  661. std::string realPath = getRealPath(outputPath);
  662. if (endsWith(realPath.c_str(), ext.c_str()))
  663. {
  664. _fileOutputPath.assign(realPath);
  665. }
  666. else if (endsWith(outputPath.c_str(), "/"))
  667. {
  668. std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));
  669. _fileOutputPath.assign(outputPath);
  670. _fileOutputPath.append(filenameNoExt);
  671. _fileOutputPath.append(ext);
  672. }
  673. else
  674. {
  675. std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
  676. int pos = realPath.find_last_of("/");
  677. if (pos)
  678. {
  679. _fileOutputPath = realPath.substr(0, pos);
  680. _fileOutputPath.append("/");
  681. _fileOutputPath.append(filenameNoExt);
  682. _fileOutputPath.append(ext);
  683. }
  684. }
  685. }
  686. }
  687. std::string EncoderArguments::getRealPath(const std::string& filepath)
  688. {
  689. char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
  690. realpath(filepath.c_str(), path);
  691. replace_char(path, '\\', '/');
  692. return std::string(path);
  693. }
  694. void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
  695. {
  696. for (; *str != '\0'; ++str)
  697. {
  698. if (*str == oldChar)
  699. {
  700. *str = newChar;
  701. }
  702. }
  703. }
  704. std::string concat(const std::string& a, const char* b)
  705. {
  706. std::string str(a);
  707. str.append(b);
  708. return str;
  709. }
  710. void unittestsEncoderArguments()
  711. {
  712. std::string dir = EncoderArguments::getRealPath(".");
  713. std::string exePath = EncoderArguments::getRealPath(".");
  714. exePath.append("/gameplay-encoder.exe");
  715. const char* exe = exePath.c_str();
  716. {
  717. const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
  718. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  719. assert(equals(args.getAnimationId("root"), ("movements")));
  720. assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
  721. assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
  722. }
  723. {
  724. // Test with only input file name (relative)
  725. const char* argv[] = {exe, "input.fbx"};
  726. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  727. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  728. assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
  729. equals(args.getOutputDirPath(), dir);
  730. }
  731. {
  732. // Test specifying a relative output path
  733. const char* argv[] = {exe, "input.fbx", "output.gpb"};
  734. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  735. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  736. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  737. }
  738. {
  739. // Test specifying a relative output path
  740. const char* argv[] = {exe, "input.fbx", "output.gpb"};
  741. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  742. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  743. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  744. }
  745. {
  746. // Test specifying a relative output path to a directory
  747. const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
  748. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  749. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  750. assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
  751. }
  752. {
  753. // Test parsing some arguments
  754. const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
  755. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  756. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  757. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  758. assert(args.textOutputEnabled());
  759. }
  760. {
  761. // Test output file with no file extension
  762. const char* argv[] = {exe, "input.fbx", "output"};
  763. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  764. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  765. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  766. }
  767. {
  768. // Test output file with wrong file extension
  769. const char* argv[] = {exe, "input.fbx", "output.fbx"};
  770. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  771. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  772. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  773. }
  774. }
  775. }