EncoderArguments.cpp 22 KB

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