EncoderArguments.cpp 25 KB

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