EncoderArguments.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  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. {
  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. "Encoder version: " ENCODER_VERSION "\n\n" \
  207. "Supported file extensions:\n" \
  208. " .fbx\t(FBX scenes)\n" \
  209. " .ttf\t(TrueType fonts)\n" \
  210. "\n" \
  211. "General options:\n" \
  212. " -v <verbosity>\tVerbosity level (0-4).\n" \
  213. "\n" \
  214. "FBX file options:\n" \
  215. " -i <id>\tFilter by node ID.\n" \
  216. " -t\t\tWrite text/xml.\n" \
  217. " -g:auto\tAutomatically group animation channels into a new animation.\n" \
  218. " -g:none\tDo not prompt to group animations.\n" \
  219. " -g <node id> <animation id>\n" \
  220. "\t\tGroup all animation channels targeting the nodes into a \n" \
  221. "\t\tnew animation.\n" \
  222. " -m\t\tOutput material file for scene.\n" \
  223. " -tb <node id>\n" \
  224. "\t\tGenerates tangents and binormals for the given node.\n" \
  225. " -oa\n" \
  226. "\t\tOptimizes animations by analyzing animation channel data and\n" \
  227. "\t\tremoving any channels that contain default/identity values\n" \
  228. "\t\tand removing any duplicate contiguous keyframes, which are \n" \
  229. "\t\tcommon when exporting baked animation data.\n" \
  230. " -h <size> \"<node ids>\" <filename>\n" \
  231. "\t\tGenerates a single heightmap image using meshes from the \n" \
  232. "\t\tspecified nodes. \n" \
  233. "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
  234. "\t\tindicating the dimensions of the produced heightmap image.\n" \
  235. "\t\t<node ids> should be in quotes with a space between each id.\n" \
  236. "\t\tFilename is the name of the image (PNG) to be saved.\n" \
  237. "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
  238. "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
  239. "\n" \
  240. "Normal map options:\n" \
  241. " -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
  242. " -s\t\tSize/resolution of the input heightmap image (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\". \n" \
  246. "\t\tThe Y value represents the maximum possible height value of a \n" \
  247. "\t\tfull intensity heightmap pixel.\n" \
  248. "\n" \
  249. " \t\tNormal map generation can be used to create object-space normal maps from \n" \
  250. " \t\theightmap images. Heightmaps must be in either PNG format (where the \n" \
  251. " \t\tintensity of each pixel represents a height value), or in RAW format \n" \
  252. " \t\t(8 or 16-bit), which is a common headerless format supported by most \n" \
  253. " \t\tterrain 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\t\tFormat of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
  259. "\n");
  260. exit(8);
  261. }
  262. bool EncoderArguments::fontPreviewEnabled() const
  263. {
  264. return _fontPreview;
  265. }
  266. Font::FontFormat EncoderArguments::getFontFormat() const
  267. {
  268. return _fontFormat;
  269. }
  270. bool EncoderArguments::textOutputEnabled() const
  271. {
  272. return _textOutput;
  273. }
  274. bool EncoderArguments::optimizeAnimationsEnabled() const
  275. {
  276. return _optimizeAnimations;
  277. }
  278. bool EncoderArguments::outputMaterialEnabled() const
  279. {
  280. return _outputMaterial;
  281. }
  282. const char* EncoderArguments::getNodeId() const
  283. {
  284. if (_nodeId.length() == 0)
  285. {
  286. return NULL;
  287. }
  288. return _nodeId.c_str();
  289. }
  290. std::vector<unsigned int> EncoderArguments::getFontSizes() const
  291. {
  292. return _fontSizes;
  293. }
  294. EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
  295. {
  296. if (_filePath.length() < 5)
  297. {
  298. return FILEFORMAT_UNKNOWN;
  299. }
  300. // Extract the extension
  301. std::string ext = "";
  302. size_t pos = _filePath.find_last_of(".");
  303. if (pos != std::string::npos)
  304. {
  305. ext = _filePath.substr(pos + 1);
  306. }
  307. for (size_t i = 0; i < ext.size(); ++i)
  308. ext[i] = (char)tolower(ext[i]);
  309. // Match every supported extension with its format constant
  310. if (ext.compare("dae") == 0)
  311. {
  312. return FILEFORMAT_DAE;
  313. }
  314. if (ext.compare("fbx") == 0)
  315. {
  316. return FILEFORMAT_FBX;
  317. }
  318. if (ext.compare("ttf") == 0)
  319. {
  320. return FILEFORMAT_TTF;
  321. }
  322. if (ext.compare("gpb") == 0)
  323. {
  324. return FILEFORMAT_GPB;
  325. }
  326. if (ext.compare("png") == 0)
  327. {
  328. return FILEFORMAT_PNG;
  329. }
  330. if (ext.compare("raw") == 0)
  331. {
  332. return FILEFORMAT_RAW;
  333. }
  334. return FILEFORMAT_UNKNOWN;
  335. }
  336. void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
  337. {
  338. const std::string& str = options[*index];
  339. if (str.length() == 0 && str[0] != '-')
  340. {
  341. return;
  342. }
  343. switch (str[1])
  344. {
  345. case 'f':
  346. if (str.compare("-f:b") == 0)
  347. {
  348. _fontFormat = Font::BITMAP;
  349. }
  350. else if (str.compare("-f:d") == 0)
  351. {
  352. _fontFormat = Font::DISTANCE_FIELD;
  353. }
  354. break;
  355. case 'g':
  356. if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
  357. {
  358. _animationGrouping = ANIMATIONGROUP_AUTO;
  359. }
  360. else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
  361. {
  362. _animationGrouping = ANIMATIONGROUP_OFF;
  363. }
  364. else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
  365. {
  366. // read two strings, make sure not to go out of bounds
  367. if ((*index + 2) >= options.size())
  368. {
  369. LOG(1, "Error: -g requires 2 arguments.\n");
  370. _parseError = true;
  371. return;
  372. }
  373. (*index)++;
  374. _groupAnimationNodeId.push_back(options[*index]);
  375. (*index)++;
  376. _groupAnimationAnimationId.push_back(options[*index]);
  377. }
  378. break;
  379. case 'i':
  380. // Node ID
  381. (*index)++;
  382. if (*index < options.size())
  383. {
  384. _nodeId.assign(options[*index]);
  385. }
  386. else
  387. {
  388. LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
  389. _parseError = true;
  390. return;
  391. }
  392. break;
  393. case 'o':
  394. // Optimization flag
  395. if (str == "-oa")
  396. {
  397. // Optimize animations
  398. _optimizeAnimations = true;
  399. }
  400. break;
  401. case 'h':
  402. {
  403. bool isHighPrecision = str.compare("-hp") == 0;
  404. if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
  405. {
  406. (*index)++;
  407. if (*index < (options.size() + 2))
  408. {
  409. _heightmaps.resize(_heightmaps.size() + 1);
  410. HeightmapOption& heightmap = _heightmaps.back();
  411. heightmap.isHighPrecision = isHighPrecision;
  412. // Read heightmap size
  413. std::vector<std::string> parts;
  414. splitString(options[*index].c_str(), &parts);
  415. if (parts.size() != 2)
  416. {
  417. LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
  418. _parseError = true;
  419. return;
  420. }
  421. heightmap.width = atoi(parts[0].c_str());
  422. heightmap.height = atoi(parts[1].c_str());
  423. // Put some artificial bounds on heightmap dimensions
  424. if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
  425. {
  426. 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);
  427. _parseError = true;
  428. return;
  429. }
  430. // Split node id list into tokens
  431. (*index)++;
  432. splitString(options[*index].c_str(), &heightmap.nodeIds);
  433. // Store output filename
  434. (*index)++;
  435. heightmap.filename = options[*index];
  436. if (heightmap.filename.empty())
  437. {
  438. LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
  439. _parseError = true;
  440. return;
  441. }
  442. // Ensure the output filename has a .png extention
  443. if (heightmap.filename.length() > 5)
  444. {
  445. const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
  446. if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
  447. heightmap.filename += ".png";
  448. }
  449. else
  450. heightmap.filename += ".png";
  451. }
  452. else
  453. {
  454. LOG(1, "Error: missing argument for -h|-heightmap.\n");
  455. _parseError = true;
  456. return;
  457. }
  458. }
  459. }
  460. break;
  461. case 'm':
  462. if (str.compare("-m") == 0)
  463. {
  464. // generate a material file
  465. _outputMaterial = true;
  466. }
  467. break;
  468. case 'n':
  469. _normalMap = true;
  470. break;
  471. case 'w':
  472. {
  473. // Read world size
  474. (*index)++;
  475. if (*index >= options.size())
  476. {
  477. LOG(1, "Error: missing world size argument for -w.\n");
  478. _parseError = true;
  479. return;
  480. }
  481. std::vector<std::string> parts;
  482. splitString(options[*index].c_str(), &parts);
  483. if (parts.size() != 3)
  484. {
  485. LOG(1, "Error: invalid world size argument for -w.\n");
  486. _parseError = true;
  487. return;
  488. }
  489. _heightmapWorldSize.x = (float)atof(parts[0].c_str());
  490. _heightmapWorldSize.y = (float)atof(parts[1].c_str());
  491. _heightmapWorldSize.z = (float)atof(parts[2].c_str());
  492. if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
  493. {
  494. LOG(1, "Error: invalid world size argument for -w.\n");
  495. _parseError = true;
  496. return;
  497. }
  498. }
  499. break;
  500. case 'p':
  501. _fontPreview = true;
  502. break;
  503. case 's':
  504. if (_normalMap)
  505. {
  506. (*index)++;
  507. if (*index >= options.size())
  508. {
  509. LOG(1, "Error: missing argument for -s.\n");
  510. _parseError = true;
  511. return;
  512. }
  513. // Heightmap size
  514. std::vector<std::string> parts;
  515. splitString(options[*index].c_str(), &parts);
  516. if (parts.size() != 2 ||
  517. (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
  518. (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
  519. {
  520. LOG(1, "Error: invalid argument for -s.\n");
  521. _parseError = true;
  522. return;
  523. }
  524. }
  525. else
  526. {
  527. // Font Sizes
  528. // old format was -s##
  529. const char* sizes = NULL;
  530. if (str.length() > 2)
  531. {
  532. char n = str[2];
  533. if (n > '0' && n <= '9')
  534. {
  535. sizes = str.c_str() + 2;
  536. }
  537. }
  538. else
  539. {
  540. (*index)++;
  541. if (*index < options.size())
  542. {
  543. sizes = options[*index].c_str();
  544. }
  545. }
  546. if (sizes == NULL)
  547. {
  548. LOG(1, "Error: invalid format for argument: -s");
  549. _parseError = true;
  550. return;
  551. }
  552. // Parse comma-separated list of font sizes
  553. char* ptr = const_cast<char*>(sizes);
  554. std::string sizeStr;
  555. while (ptr)
  556. {
  557. char* end = strchr(ptr, ',');
  558. if (end)
  559. {
  560. sizeStr = std::string(ptr, end - ptr);
  561. ptr = end + 1;
  562. }
  563. else
  564. {
  565. sizeStr = ptr;
  566. ptr = NULL;
  567. }
  568. if (sizeStr.length() > 0)
  569. {
  570. int size = atoi(sizeStr.c_str());
  571. if (size <= 0)
  572. {
  573. LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
  574. _parseError = true;
  575. return;
  576. }
  577. _fontSizes.push_back((unsigned int)size);
  578. }
  579. }
  580. }
  581. break;
  582. case 't':
  583. if (str.compare("-t") == 0)
  584. {
  585. _textOutput = true;
  586. }
  587. else if (str.compare("-tb") == 0)
  588. {
  589. if ((*index + 1) >= options.size())
  590. {
  591. LOG(1, "Error: -tb requires 1 argument.\n");
  592. _parseError = true;
  593. return;
  594. }
  595. (*index)++;
  596. std::string nodeId = options[*index];
  597. if (nodeId.length() > 0)
  598. {
  599. _tangentBinormalId.insert(nodeId);
  600. }
  601. }
  602. break;
  603. case 'v':
  604. (*index)++;
  605. if (*index < options.size())
  606. {
  607. __logVerbosity = atoi(options[*index].c_str());
  608. if (__logVerbosity < 0)
  609. __logVerbosity = 0;
  610. else if (__logVerbosity > 4)
  611. __logVerbosity = 4;
  612. }
  613. break;
  614. default:
  615. break;
  616. }
  617. }
  618. void EncoderArguments::setInputfilePath(const std::string& inputPath)
  619. {
  620. _filePath.assign(getRealPath(inputPath));
  621. }
  622. void EncoderArguments::setOutputfilePath(const std::string& outputPath)
  623. {
  624. std::string ext = getOutputFileExtension();
  625. if (outputPath.size() > 0 && outputPath[0] != '\0')
  626. {
  627. std::string realPath = getRealPath(outputPath);
  628. if (endsWith(realPath.c_str(), ext.c_str()))
  629. {
  630. _fileOutputPath.assign(realPath);
  631. }
  632. else if (endsWith(outputPath.c_str(), "/"))
  633. {
  634. std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));
  635. _fileOutputPath.assign(outputPath);
  636. _fileOutputPath.append(filenameNoExt);
  637. _fileOutputPath.append(ext);
  638. }
  639. else
  640. {
  641. std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
  642. int pos = realPath.find_last_of("/");
  643. if (pos)
  644. {
  645. _fileOutputPath = realPath.substr(0, pos);
  646. _fileOutputPath.append("/");
  647. _fileOutputPath.append(filenameNoExt);
  648. _fileOutputPath.append(ext);
  649. }
  650. }
  651. }
  652. }
  653. std::string EncoderArguments::getRealPath(const std::string& filepath)
  654. {
  655. char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
  656. realpath(filepath.c_str(), path);
  657. replace_char(path, '\\', '/');
  658. return std::string(path);
  659. }
  660. void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
  661. {
  662. for (; *str != '\0'; ++str)
  663. {
  664. if (*str == oldChar)
  665. {
  666. *str = newChar;
  667. }
  668. }
  669. }
  670. std::string concat(const std::string& a, const char* b)
  671. {
  672. std::string str(a);
  673. str.append(b);
  674. return str;
  675. }
  676. void unittestsEncoderArguments()
  677. {
  678. std::string dir = EncoderArguments::getRealPath(".");
  679. std::string exePath = EncoderArguments::getRealPath(".");
  680. exePath.append("/gameplay-encoder.exe");
  681. const char* exe = exePath.c_str();
  682. {
  683. const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
  684. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  685. assert(equals(args.getAnimationId("root"), ("movements")));
  686. assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
  687. assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
  688. }
  689. {
  690. // Test with only input file name (relative)
  691. const char* argv[] = {exe, "input.fbx"};
  692. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  693. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  694. assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
  695. equals(args.getOutputDirPath(), dir);
  696. }
  697. {
  698. // Test specifying a relative output path
  699. const char* argv[] = {exe, "input.fbx", "output.gpb"};
  700. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  701. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  702. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  703. }
  704. {
  705. // Test specifying a relative output path
  706. const char* argv[] = {exe, "input.fbx", "output.gpb"};
  707. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  708. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  709. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  710. }
  711. {
  712. // Test specifying a relative output path to a directory
  713. const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
  714. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  715. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  716. assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
  717. }
  718. {
  719. // Test parsing some arguments
  720. const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
  721. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  722. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  723. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  724. assert(args.textOutputEnabled());
  725. }
  726. {
  727. // Test output file with no file extension
  728. const char* argv[] = {exe, "input.fbx", "output"};
  729. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  730. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  731. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  732. }
  733. {
  734. // Test output file with wrong file extension
  735. const char* argv[] = {exe, "input.fbx", "output.fbx"};
  736. EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
  737. assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
  738. assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
  739. }
  740. }
  741. }