EncoderArguments.cpp 23 KB

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