EncoderArguments.cpp 25 KB

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