EncoderArguments.cpp 23 KB

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