UVAtlas.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197
  1. //--------------------------------------------------------------------------------------
  2. // File: UVAtlas.cpp
  3. //
  4. // UVAtlas command-line tool (sample for UVAtlas library)
  5. //
  6. // Copyright (c) Microsoft Corporation. All rights reserved.
  7. //
  8. // http://go.microsoft.com/fwlink/?LinkID=512686
  9. //--------------------------------------------------------------------------------------
  10. #define NOMINMAX
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <assert.h>
  14. #include <conio.h>
  15. #include <memory>
  16. #include <list>
  17. #include <dxgiformat.h>
  18. #include "uvatlas.h"
  19. #include "directxtex.h"
  20. #include "Mesh.h"
  21. #include "WaveFrontReader.h"
  22. using namespace DirectX;
  23. enum OPTIONS // Note: dwOptions below assumes 32 or less options.
  24. {
  25. OPT_QUALITY = 1,
  26. OPT_MAXCHARTS,
  27. OPT_MAXSTRETCH,
  28. OPT_GUTTER,
  29. OPT_WIDTH,
  30. OPT_HEIGHT,
  31. OPT_TOPOLOGICAL_ADJ,
  32. OPT_GEOMETRIC_ADJ,
  33. OPT_NORMALS,
  34. OPT_WEIGHT_BY_AREA,
  35. OPT_WEIGHT_BY_EQUAL,
  36. OPT_TANGENTS,
  37. OPT_CTF,
  38. OPT_COLOR_MESH,
  39. OPT_UV_MESH,
  40. OPT_IMT_TEXFILE,
  41. OPT_IMT_VERTEX,
  42. OPT_SDKMESH,
  43. OPT_CMO,
  44. OPT_VBO,
  45. OPT_OUTPUTFILE,
  46. OPT_CLOCKWISE,
  47. OPT_OVERWRITE,
  48. OPT_NODDS,
  49. OPT_FLIP,
  50. OPT_FLIPTC,
  51. OPT_NOLOGO,
  52. OPT_MAX
  53. };
  54. static_assert( OPT_MAX <= 32, "dwOptions is a DWORD bitfield" );
  55. enum CHANNELS
  56. {
  57. CHANNEL_NONE = 0,
  58. CHANNEL_NORMAL,
  59. CHANNEL_COLOR,
  60. CHANNEL_TEXCOORD,
  61. };
  62. struct SConversion
  63. {
  64. WCHAR szSrc [MAX_PATH];
  65. };
  66. struct SValue
  67. {
  68. LPCWSTR pName;
  69. DWORD dwValue;
  70. };
  71. static const XMFLOAT3 g_ColorList[8] =
  72. {
  73. XMFLOAT3( 1.0f, 0.5f, 0.5f ),
  74. XMFLOAT3( 0.5f, 1.0f, 0.5f ),
  75. XMFLOAT3( 1.0f, 1.0f, 0.5f ),
  76. XMFLOAT3( 0.5f, 1.0f, 1.0f ),
  77. XMFLOAT3( 1.0f, 0.5f, 0.75f ),
  78. XMFLOAT3( 0.0f, 0.5f, 0.75f ),
  79. XMFLOAT3( 0.5f, 0.5f, 0.75f ),
  80. XMFLOAT3( 0.5f, 0.5f, 1.0f ),
  81. };
  82. //////////////////////////////////////////////////////////////////////////////
  83. //////////////////////////////////////////////////////////////////////////////
  84. //////////////////////////////////////////////////////////////////////////////
  85. SValue g_pOptions[] =
  86. {
  87. { L"q", OPT_QUALITY },
  88. { L"n", OPT_MAXCHARTS },
  89. { L"st", OPT_MAXSTRETCH },
  90. { L"g", OPT_GUTTER },
  91. { L"w", OPT_WIDTH },
  92. { L"h", OPT_HEIGHT },
  93. { L"ta", OPT_TOPOLOGICAL_ADJ },
  94. { L"ga", OPT_GEOMETRIC_ADJ },
  95. { L"nn", OPT_NORMALS },
  96. { L"na", OPT_WEIGHT_BY_AREA },
  97. { L"ne", OPT_WEIGHT_BY_EQUAL },
  98. { L"tt", OPT_TANGENTS },
  99. { L"tb", OPT_CTF },
  100. { L"c", OPT_COLOR_MESH },
  101. { L"t", OPT_UV_MESH },
  102. { L"it", OPT_IMT_TEXFILE },
  103. { L"iv", OPT_IMT_VERTEX },
  104. { L"o", OPT_OUTPUTFILE },
  105. { L"sdkmesh", OPT_SDKMESH },
  106. { L"cmo", OPT_CMO },
  107. { L"vbo", OPT_VBO },
  108. { L"cw", OPT_CLOCKWISE },
  109. { L"y", OPT_OVERWRITE },
  110. { L"nodds", OPT_NODDS },
  111. { L"flip", OPT_FLIP },
  112. { L"fliptc", OPT_FLIPTC },
  113. { L"nologo", OPT_NOLOGO },
  114. { nullptr, 0 }
  115. };
  116. //////////////////////////////////////////////////////////////////////////////
  117. //////////////////////////////////////////////////////////////////////////////
  118. //////////////////////////////////////////////////////////////////////////////
  119. #pragma prefast(disable : 26018, "Only used with static internal arrays")
  120. DWORD LookupByName(const WCHAR *pName, const SValue *pArray)
  121. {
  122. while(pArray->pName)
  123. {
  124. if(!_wcsicmp(pName, pArray->pName))
  125. return pArray->dwValue;
  126. pArray++;
  127. }
  128. return 0;
  129. }
  130. const WCHAR* LookupByValue(DWORD pValue, const SValue *pArray)
  131. {
  132. while(pArray->pName)
  133. {
  134. if(pValue == pArray->dwValue)
  135. return pArray->pName;
  136. pArray++;
  137. }
  138. return L"";
  139. }
  140. void PrintLogo()
  141. {
  142. wprintf( L"Microsoft (R) UVAtlas Command-line Tool\n");
  143. wprintf( L"Copyright (C) Microsoft Corp. All rights reserved.\n");
  144. wprintf( L"\n");
  145. }
  146. void PrintUsage()
  147. {
  148. PrintLogo();
  149. wprintf( L"Usage: uvatlas <options> <files>\n");
  150. wprintf( L"\n");
  151. wprintf( L" -q <level> sets quality level to DEFAULT, FAST or QUALITY\n");
  152. wprintf( L" -n <number> maximum number of charts to generate (def: 0)\n");
  153. wprintf( L" -st <float> maximum amount of stretch 0.0 to 1.0 (def: 0.16667)\n");
  154. wprintf( L" -g <float> the gutter width betwen charts in texels (def: 2.0)\n");
  155. wprintf( L" -w <number> texture width (def: 512)\n");
  156. wprintf( L" -h <number> texture height (def: 512)\n");
  157. wprintf( L" -ta | -ga generate topological vs. geometric adjancecy (def: ta)\n");
  158. wprintf( L" -nn | -na | -ne generate normals weighted by angle/area/equal\n" );
  159. wprintf( L" -tt generate tangents\n");
  160. wprintf( L" -tb generate tangents & bi-tangents\n");
  161. wprintf( L" -cw faces are clockwise (defaults to counter-clockwise)\n");
  162. wprintf( L" -c generate mesh with colors showing charts\n");
  163. wprintf( L" -t generates a separate mesh with uvs - (*_texture)\n");
  164. wprintf( L" -it <filename> calculate IMT for the mesh using this texture map\n");
  165. wprintf( L" -iv <channel> calculate IMT using per-vertex data\n");
  166. wprintf( L" NORMAL, COLOR, TEXCOORD\n");
  167. wprintf( L" -sdkmesh|-cmo|-vbo output file type\n");
  168. wprintf( L" -nodds prevents extension renaming in exported materials\n");
  169. wprintf( L" -flip | -fliptc reverse winding of faces and/or flips texcoords\n");
  170. wprintf( L" -o <filename> output filename\n");
  171. wprintf( L" -y overwrite existing output file (if any)\n");
  172. wprintf( L" -nologo suppress copyright message\n");
  173. wprintf( L"\n");
  174. }
  175. //--------------------------------------------------------------------------------------
  176. HRESULT __cdecl UVAtlasCallback( float fPercentDone )
  177. {
  178. static ULONGLONG s_lastTick = 0;
  179. ULONGLONG tick = GetTickCount64();
  180. if ( ( tick - s_lastTick ) > 1000 )
  181. {
  182. wprintf( L"%.2f%% \r", fPercentDone * 100 );
  183. s_lastTick = tick;
  184. }
  185. if ( _kbhit() )
  186. {
  187. if ( _getch() == 27 )
  188. {
  189. wprintf(L"*** ABORT ***");
  190. return E_ABORT;
  191. }
  192. }
  193. return S_OK;
  194. }
  195. //--------------------------------------------------------------------------------------
  196. HRESULT LoadFromOBJ(const WCHAR* szFilename, std::unique_ptr<Mesh>& inMesh, std::vector<Mesh::Material>& inMaterial, DWORD options )
  197. {
  198. WaveFrontReader<uint32_t> wfReader;
  199. HRESULT hr = wfReader.Load(szFilename, (options & (1 << OPT_CLOCKWISE)) ? false : true );
  200. if (FAILED(hr))
  201. return hr;
  202. inMesh.reset(new (std::nothrow) Mesh);
  203. if (!inMesh)
  204. return E_OUTOFMEMORY;
  205. if (wfReader.indices.empty() || wfReader.vertices.empty())
  206. return E_FAIL;
  207. hr = inMesh->SetIndexData(wfReader.indices.size() / 3, &wfReader.indices.front(),
  208. wfReader.attributes.empty() ? nullptr : &wfReader.attributes.front());
  209. if (FAILED(hr))
  210. return hr;
  211. static const D3D11_INPUT_ELEMENT_DESC s_vboLayout [] =
  212. {
  213. { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  214. { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  215. { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  216. };
  217. static const D3D11_INPUT_ELEMENT_DESC s_vboLayoutAlt [] =
  218. {
  219. { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  220. { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
  221. };
  222. const D3D11_INPUT_ELEMENT_DESC* layout = s_vboLayout;
  223. size_t nDecl = _countof(s_vboLayout);
  224. if (!wfReader.hasNormals && !wfReader.hasTexcoords)
  225. {
  226. nDecl = 1;
  227. }
  228. else if (wfReader.hasNormals && !wfReader.hasTexcoords)
  229. {
  230. nDecl = 2;
  231. }
  232. else if (!wfReader.hasNormals && wfReader.hasTexcoords)
  233. {
  234. layout = s_vboLayoutAlt;
  235. nDecl = _countof(s_vboLayoutAlt);
  236. }
  237. VBReader vbr;
  238. hr = vbr.Initialize(layout, nDecl);
  239. if (FAILED(hr))
  240. return hr;
  241. hr = vbr.AddStream(&wfReader.vertices.front(), wfReader.vertices.size(), 0, sizeof(WaveFrontReader<uint32_t>::Vertex));
  242. if (FAILED(hr))
  243. return hr;
  244. hr = inMesh->SetVertexData(vbr, wfReader.vertices.size());
  245. if (FAILED(hr))
  246. return hr;
  247. if ( !wfReader.materials.empty() )
  248. {
  249. inMaterial.clear();
  250. inMaterial.reserve(wfReader.materials.size());
  251. for (auto it = wfReader.materials.cbegin(); it != wfReader.materials.cend(); ++it)
  252. {
  253. Mesh::Material mtl;
  254. memset(&mtl, 0, sizeof(mtl));
  255. mtl.name = it->strName;
  256. mtl.specularPower = (it->bSpecular) ? float(it->nShininess) : 1.f;
  257. mtl.alpha = it->fAlpha;
  258. mtl.ambientColor = it->vAmbient;
  259. mtl.diffuseColor = it->vDiffuse;
  260. mtl.specularColor = (it->bSpecular) ? it->vSpecular : XMFLOAT3(0.f, 0.f, 0.f);
  261. WCHAR texture[_MAX_PATH] = { 0 };
  262. if (*it->strTexture)
  263. {
  264. WCHAR txext[_MAX_EXT];
  265. WCHAR txfname[_MAX_FNAME];
  266. _wsplitpath_s(it->strTexture, nullptr, 0, nullptr, 0, txfname, _MAX_FNAME, txext, _MAX_EXT);
  267. if (!(options & (1 << OPT_NODDS)))
  268. {
  269. wcscpy_s(txext, L".dds");
  270. }
  271. _wmakepath_s(texture, nullptr, nullptr, txfname, txext);
  272. }
  273. mtl.texture = texture;
  274. inMaterial.push_back(mtl);
  275. }
  276. }
  277. return S_OK;
  278. }
  279. //--------------------------------------------------------------------------------------
  280. // Entry-point
  281. //--------------------------------------------------------------------------------------
  282. #pragma prefast(disable : 28198, "Command-line tool, frees all memory on exit")
  283. int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
  284. {
  285. // Parameters and defaults
  286. size_t maxCharts = 0;
  287. float maxStretch = 0.16667f;
  288. float gutter = 2.f;
  289. size_t width = 512;
  290. size_t height = 512;
  291. CHANNELS perVertex = CHANNEL_NONE;
  292. DWORD uvOptions = UVATLAS_DEFAULT;
  293. WCHAR szTexFile[MAX_PATH] = { 0 };
  294. WCHAR szOutputFile[MAX_PATH] = { 0 };
  295. // Initialize COM (needed for WIC)
  296. HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  297. if( FAILED(hr) )
  298. {
  299. wprintf( L"Failed to initialize COM (%08X)\n", hr);
  300. return 1;
  301. }
  302. // Process command line
  303. DWORD dwOptions = 0;
  304. std::list<SConversion> conversion;
  305. for(int iArg = 1; iArg < argc; iArg++)
  306. {
  307. PWSTR pArg = argv[iArg];
  308. if(('-' == pArg[0]) || ('/' == pArg[0]))
  309. {
  310. pArg++;
  311. PWSTR pValue;
  312. for(pValue = pArg; *pValue && (':' != *pValue); pValue++);
  313. if(*pValue)
  314. *pValue++ = 0;
  315. DWORD dwOption = LookupByName(pArg, g_pOptions);
  316. if(!dwOption || (dwOptions & (1 << dwOption)))
  317. {
  318. wprintf( L"ERROR: unknown command-line option '%ls'\n\n", pArg);
  319. PrintUsage();
  320. return 1;
  321. }
  322. dwOptions |= (1 << dwOption);
  323. if ( OPT_NOLOGO != dwOption && OPT_OVERWRITE != dwOption
  324. && OPT_CLOCKWISE != dwOption && OPT_NODDS != dwOption
  325. && OPT_FLIP != dwOption && OPT_FLIPTC != dwOption
  326. && OPT_NORMALS != dwOption && OPT_WEIGHT_BY_AREA != dwOption && OPT_WEIGHT_BY_EQUAL != dwOption
  327. && OPT_TANGENTS != dwOption && OPT_CTF != dwOption
  328. && OPT_TOPOLOGICAL_ADJ != dwOption && OPT_GEOMETRIC_ADJ != dwOption
  329. && OPT_COLOR_MESH != dwOption && OPT_UV_MESH != dwOption
  330. && OPT_SDKMESH != dwOption && OPT_CMO != dwOption && OPT_VBO != dwOption )
  331. {
  332. if(!*pValue)
  333. {
  334. if((iArg + 1 >= argc))
  335. {
  336. wprintf( L"ERROR: missing value for command-line option '%ls'\n\n", pArg);
  337. PrintUsage();
  338. return 1;
  339. }
  340. iArg++;
  341. pValue = argv[iArg];
  342. }
  343. }
  344. switch(dwOption)
  345. {
  346. case OPT_QUALITY:
  347. if ( !_wcsicmp( pValue, L"DEFAULT" ) )
  348. {
  349. uvOptions = UVATLAS_DEFAULT;
  350. }
  351. else if ( !_wcsicmp( pValue, L"FAST" ) )
  352. {
  353. uvOptions = UVATLAS_GEODESIC_FAST;
  354. }
  355. else if ( !_wcsicmp( pValue, L"QUALITY" ) )
  356. {
  357. uvOptions = UVATLAS_GEODESIC_QUALITY;
  358. }
  359. else
  360. {
  361. wprintf( L"Invalid value specified with -q (%ls)\n", pValue);
  362. return 1;
  363. }
  364. break;
  365. case OPT_MAXCHARTS:
  366. if (swscanf_s(pValue, L"%Iu", &maxCharts) != 1)
  367. {
  368. wprintf( L"Invalid value specified with -n (%ls)\n", pValue);
  369. return 1;
  370. }
  371. break;
  372. case OPT_MAXSTRETCH:
  373. if (swscanf_s(pValue, L"%f", &maxStretch) != 1
  374. || maxStretch < 0.f
  375. || maxStretch > 1.f )
  376. {
  377. wprintf( L"Invalid value specified with -st (%ls)\n", pValue);
  378. return 1;
  379. }
  380. break;
  381. case OPT_GUTTER:
  382. if (swscanf_s(pValue, L"%f", &gutter) != 1
  383. || gutter < 0.f )
  384. {
  385. wprintf( L"Invalid value specified with -g (%ls)\n", pValue);
  386. return 1;
  387. }
  388. break;
  389. case OPT_WIDTH:
  390. if (swscanf_s(pValue, L"%Iu", &width) != 1)
  391. {
  392. wprintf( L"Invalid value specified with -w (%ls)\n", pValue);
  393. return 1;
  394. }
  395. break;
  396. case OPT_HEIGHT:
  397. if (swscanf_s(pValue, L"%Iu", &height) != 1)
  398. {
  399. wprintf( L"Invalid value specified with -h (%ls)\n", pValue);
  400. return 1;
  401. }
  402. break;
  403. case OPT_WEIGHT_BY_AREA:
  404. if (dwOptions & (1 << OPT_WEIGHT_BY_EQUAL))
  405. {
  406. wprintf(L"Can only use one of nn, na, or ne\n");
  407. return 1;
  408. }
  409. dwOptions |= (1 << OPT_NORMALS);
  410. break;
  411. case OPT_WEIGHT_BY_EQUAL:
  412. if (dwOptions & (1 << OPT_WEIGHT_BY_AREA))
  413. {
  414. wprintf(L"Can only use one of nn, na, or ne\n");
  415. return 1;
  416. }
  417. dwOptions |= (1 << OPT_NORMALS);
  418. break;
  419. case OPT_IMT_TEXFILE:
  420. if ( dwOptions & (1 << OPT_IMT_VERTEX) )
  421. {
  422. wprintf( L"Cannot use both if and iv at the same time\n" );
  423. return 1;
  424. }
  425. wcscpy_s(szTexFile, MAX_PATH, pValue);
  426. break;
  427. case OPT_IMT_VERTEX:
  428. if ( dwOptions & (1 << OPT_IMT_TEXFILE) )
  429. {
  430. wprintf( L"Cannot use both if and iv at the same time\n" );
  431. return 1;
  432. }
  433. if ( !_wcsicmp( pValue, L"COLOR" ) )
  434. {
  435. perVertex = CHANNEL_COLOR;
  436. }
  437. else if ( !_wcsicmp( pValue, L"NORMAL" ) )
  438. {
  439. perVertex = CHANNEL_NORMAL;
  440. }
  441. else if ( !_wcsicmp( pValue, L"TEXCOORD" ) )
  442. {
  443. perVertex = CHANNEL_TEXCOORD;
  444. }
  445. else
  446. {
  447. wprintf( L"Invalid value specified with -iv (%ls)\n", pValue);
  448. return 1;
  449. }
  450. break;
  451. case OPT_OUTPUTFILE:
  452. wcscpy_s(szOutputFile, MAX_PATH, pValue);
  453. break;
  454. case OPT_TOPOLOGICAL_ADJ:
  455. if (dwOptions & (1 << OPT_GEOMETRIC_ADJ))
  456. {
  457. wprintf(L"Cannot use both ta and ga at the same time\n");
  458. return 1;
  459. }
  460. break;
  461. case OPT_GEOMETRIC_ADJ:
  462. if (dwOptions & (1 << OPT_TOPOLOGICAL_ADJ))
  463. {
  464. wprintf(L"Cannot use both ta and ga at the same time\n");
  465. return 1;
  466. }
  467. break;
  468. case OPT_SDKMESH:
  469. if ( dwOptions & ( (1 << OPT_VBO) | (1 << OPT_CMO) ) )
  470. {
  471. wprintf( L"Can only use one of sdkmesh, cmo, or vbo\n" );
  472. return 1;
  473. }
  474. break;
  475. case OPT_CMO:
  476. if ( dwOptions & ( (1 << OPT_VBO) | (1 << OPT_SDKMESH) ) )
  477. {
  478. wprintf( L"Can only use one of sdkmesh, cmo, or vbo\n" );
  479. return 1;
  480. }
  481. break;
  482. case OPT_VBO:
  483. if ( dwOptions & ( (1 << OPT_SDKMESH) | (1 << OPT_CMO) ) )
  484. {
  485. wprintf( L"Can only use one of sdkmesh, cmo, or vbo\n" );
  486. return 1;
  487. }
  488. break;
  489. case OPT_FLIP:
  490. if ( dwOptions & (1 << OPT_FLIPTC) )
  491. {
  492. wprintf(L"Can only use flip or fliptc\n");
  493. return 1;
  494. }
  495. break;
  496. case OPT_FLIPTC:
  497. if (dwOptions & (1 << OPT_FLIP))
  498. {
  499. wprintf(L"Can only use flip or fliptc\n");
  500. return 1;
  501. }
  502. break;
  503. }
  504. }
  505. else
  506. {
  507. SConversion conv;
  508. wcscpy_s(conv.szSrc, MAX_PATH, pArg);
  509. conversion.push_back(conv);
  510. }
  511. }
  512. if(conversion.empty())
  513. {
  514. PrintUsage();
  515. return 0;
  516. }
  517. if ( *szOutputFile && conversion.size() > 1 )
  518. {
  519. wprintf( L"Cannot use -o with multiple input files\n");
  520. return 1;
  521. }
  522. if(~dwOptions & (1 << OPT_NOLOGO))
  523. PrintLogo();
  524. // Process files
  525. for( auto pConv = conversion.begin(); pConv != conversion.end(); ++pConv )
  526. {
  527. WCHAR ext[_MAX_EXT];
  528. WCHAR fname[_MAX_FNAME];
  529. _wsplitpath_s( pConv->szSrc, nullptr, 0, nullptr, 0, fname, _MAX_FNAME, ext, _MAX_EXT );
  530. if ( pConv != conversion.begin() )
  531. wprintf( L"\n");
  532. wprintf( L"reading %ls", pConv->szSrc );
  533. fflush(stdout);
  534. std::unique_ptr<Mesh> inMesh;
  535. std::vector<Mesh::Material> inMaterial;
  536. hr = E_NOTIMPL;
  537. if ( _wcsicmp( ext, L".vbo" ) == 0 )
  538. {
  539. hr = Mesh::CreateFromVBO( pConv->szSrc, inMesh );
  540. }
  541. else if ( _wcsicmp( ext, L".sdkmesh" ) == 0 )
  542. {
  543. wprintf(L"\nERROR: Importing SDKMESH files not supported\n");
  544. return 1;
  545. }
  546. else if ( _wcsicmp( ext, L".cmo" ) == 0 )
  547. {
  548. wprintf(L"\nERROR: Importing Visual Studio CMO files not supported\n");
  549. return 1;
  550. }
  551. else if ( _wcsicmp( ext, L".x" ) == 0 )
  552. {
  553. wprintf( L"\nERROR: Legacy Microsoft X files not supported\n");
  554. return 1;
  555. }
  556. else
  557. {
  558. hr = LoadFromOBJ(pConv->szSrc, inMesh, inMaterial, dwOptions);
  559. }
  560. if (FAILED(hr))
  561. {
  562. wprintf( L" FAILED (%08X)\n", hr);
  563. return 1;
  564. }
  565. size_t nVerts = inMesh->GetVertexCount();
  566. size_t nFaces = inMesh->GetFaceCount();
  567. if (!nVerts || !nFaces)
  568. {
  569. wprintf( L"\nERROR: Invalid mesh\n" );
  570. return 1;
  571. }
  572. assert(inMesh->GetPositionBuffer() != 0);
  573. assert(inMesh->GetIndexBuffer() != 0);
  574. wprintf(L"\nVerts: %Iu, nFaces: %Iu", nVerts, nFaces);
  575. // Prepare mesh for processing
  576. {
  577. // Adjacency
  578. float epsilon = (dwOptions & (1 << OPT_GEOMETRIC_ADJ)) ? 1e-5f : 0.f;
  579. hr = inMesh->GenerateAdjacency(epsilon);
  580. if (FAILED(hr))
  581. {
  582. wprintf( L"\nERROR: Failed generating adjacency (%08X)\n", hr );
  583. return 1;
  584. }
  585. // Validation
  586. std::wstring msgs;
  587. hr = inMesh->Validate( VALIDATE_BACKFACING | VALIDATE_BOWTIES, &msgs );
  588. if (!msgs.empty())
  589. {
  590. wprintf(L"\nWARNING: \n");
  591. wprintf(msgs.c_str());
  592. }
  593. // Clean
  594. hr = inMesh->Clean( true );
  595. if (FAILED(hr))
  596. {
  597. wprintf( L"\nERROR: Failed mesh clean (%08X)\n", hr );
  598. return 1;
  599. }
  600. else
  601. {
  602. size_t nNewVerts = inMesh->GetVertexCount();
  603. if (nVerts != nNewVerts)
  604. {
  605. wprintf(L" [%Iu vertex dups] ", nNewVerts - nVerts);
  606. nVerts = nNewVerts;
  607. }
  608. }
  609. }
  610. if (!inMesh->GetNormalBuffer())
  611. {
  612. dwOptions |= 1 << OPT_NORMALS;
  613. }
  614. if (!inMesh->GetTangentBuffer() && (dwOptions & (1 << OPT_CMO)))
  615. {
  616. dwOptions |= 1 << OPT_TANGENTS;
  617. }
  618. // Compute vertex normals from faces
  619. if ((dwOptions & (1 << OPT_NORMALS))
  620. || ((dwOptions & ((1 << OPT_TANGENTS) | (1 << OPT_CTF))) && !inMesh->GetNormalBuffer()) )
  621. {
  622. DWORD flags = CNORM_DEFAULT;
  623. if (dwOptions & (1 << OPT_WEIGHT_BY_EQUAL))
  624. {
  625. flags |= CNORM_WEIGHT_EQUAL;
  626. }
  627. else if (dwOptions & (1 << OPT_WEIGHT_BY_AREA))
  628. {
  629. flags |= CNORM_WEIGHT_BY_AREA;
  630. }
  631. if (dwOptions & (1 << OPT_CLOCKWISE))
  632. {
  633. flags |= CNORM_WIND_CW;
  634. }
  635. hr = inMesh->ComputeNormals( flags );
  636. if (FAILED(hr))
  637. {
  638. wprintf( L"\nERROR: Failed computing normals (flags:%1X, %08X)\n", flags, hr );
  639. return 1;
  640. }
  641. }
  642. // Compute tangents and bitangents
  643. if (dwOptions & ((1 << OPT_TANGENTS) | (1 << OPT_CTF)))
  644. {
  645. if (!inMesh->GetTexCoordBuffer())
  646. {
  647. wprintf( L"\nERROR: Computing tangents/bi-tangents requires texture coordinates\n" );
  648. return 1;
  649. }
  650. hr = inMesh->ComputeTangentFrame( (dwOptions & (1 << OPT_CTF)) ? true : false );
  651. if (FAILED(hr))
  652. {
  653. wprintf(L"\nERROR: Failed computing tangent frame (%08X)\n", hr);
  654. return 1;
  655. }
  656. }
  657. // Compute IMT
  658. std::unique_ptr<float[]> IMTData;
  659. if ( dwOptions & ((1 << OPT_IMT_TEXFILE) | (1 << OPT_IMT_VERTEX)) )
  660. {
  661. if (dwOptions & (1 << OPT_IMT_TEXFILE))
  662. {
  663. if (!inMesh->GetTexCoordBuffer())
  664. {
  665. wprintf( L"\nERROR: Computing IMT from texture requires texture coordinates\n" );
  666. return 1;
  667. }
  668. WCHAR txext[_MAX_EXT];
  669. _wsplitpath_s(szTexFile, nullptr, 0, nullptr, 0, nullptr, 0, txext, _MAX_EXT);
  670. ScratchImage iimage;
  671. if (_wcsicmp(txext, L".dds") == 0)
  672. {
  673. hr = LoadFromDDSFile(szTexFile, DDS_FLAGS_NONE, nullptr, iimage);
  674. }
  675. else if (_wcsicmp(ext, L".tga") == 0)
  676. {
  677. hr = LoadFromTGAFile(szTexFile, nullptr, iimage);
  678. }
  679. else
  680. {
  681. hr = LoadFromWICFile(szTexFile, TEX_FILTER_DEFAULT, nullptr, iimage);
  682. }
  683. if (FAILED(hr))
  684. {
  685. wprintf(L"\nWARNING: Failed to load texture for IMT (%08X):\n%ls\n", hr, szTexFile );
  686. }
  687. else
  688. {
  689. const Image* img = iimage.GetImage(0, 0, 0);
  690. ScratchImage floatImage;
  691. if (img->format != DXGI_FORMAT_R32G32B32A32_FLOAT)
  692. {
  693. hr = Convert(*iimage.GetImage(0, 0, 0), DXGI_FORMAT_R32G32B32A32_FLOAT, TEX_FILTER_DEFAULT, 0.5f, floatImage);
  694. if (FAILED(hr))
  695. {
  696. img = nullptr;
  697. wprintf(L"\nWARNING: Failed converting texture for IMT (%08X):\n%ls\n", hr, szTexFile);
  698. }
  699. else
  700. {
  701. img = floatImage.GetImage(0, 0, 0);
  702. }
  703. }
  704. if ( img )
  705. {
  706. wprintf(L"\nComputing IMT from file %ls...\n", szTexFile);
  707. IMTData.reset(new (std::nothrow) float[nFaces * 3]);
  708. if (!IMTData)
  709. {
  710. wprintf(L"\nERROR: out of memory\n");
  711. return 1;
  712. }
  713. hr = UVAtlasComputeIMTFromTexture(inMesh->GetPositionBuffer(), inMesh->GetTexCoordBuffer(), nVerts,
  714. inMesh->GetIndexBuffer(), DXGI_FORMAT_R32_UINT, nFaces,
  715. reinterpret_cast<const float*>( img->pixels ), img->width, img->height,
  716. UVATLAS_IMT_DEFAULT, UVAtlasCallback, IMTData.get());
  717. if (FAILED(hr))
  718. {
  719. IMTData.reset();
  720. wprintf(L"WARNING: Failed to compute IMT from texture (%08X):\n%ls\n", hr, szTexFile);
  721. }
  722. }
  723. }
  724. }
  725. else
  726. {
  727. const WCHAR* szChannel = L"*unknown*";
  728. const float* pSignal = nullptr;
  729. size_t signalDim = 0;
  730. size_t signalStride = 0;
  731. switch (perVertex)
  732. {
  733. case CHANNEL_NORMAL:
  734. szChannel = L"normals";
  735. if (inMesh->GetNormalBuffer())
  736. {
  737. pSignal = reinterpret_cast<const float*>(inMesh->GetNormalBuffer());
  738. signalDim = 3;
  739. signalStride = sizeof(XMFLOAT3);
  740. }
  741. break;
  742. case CHANNEL_COLOR:
  743. szChannel = L"vertex colors";
  744. if (inMesh->GetColorBuffer())
  745. {
  746. pSignal = reinterpret_cast<const float*>(inMesh->GetColorBuffer());
  747. signalDim = 4;
  748. signalStride = sizeof(XMFLOAT4);
  749. }
  750. break;
  751. case CHANNEL_TEXCOORD:
  752. szChannel = L"texture coordinates";
  753. if (inMesh->GetTexCoordBuffer())
  754. {
  755. pSignal = reinterpret_cast<const float*>(inMesh->GetTexCoordBuffer());
  756. signalDim = 2;
  757. signalStride = sizeof(XMFLOAT2);
  758. }
  759. break;
  760. }
  761. if (!pSignal)
  762. {
  763. wprintf(L"\nWARNING: Mesh does not have channel %ls for IMT\n", szChannel );
  764. }
  765. else
  766. {
  767. wprintf(L"\nComputing IMT from %ls...\n", szChannel);
  768. IMTData.reset(new (std::nothrow) float[nFaces * 3]);
  769. if (!IMTData)
  770. {
  771. wprintf(L"\nERROR: out of memory\n");
  772. return 1;
  773. }
  774. hr = UVAtlasComputeIMTFromPerVertexSignal( inMesh->GetPositionBuffer(), nVerts,
  775. inMesh->GetIndexBuffer(), DXGI_FORMAT_R32_UINT, nFaces,
  776. pSignal, signalDim, signalStride, UVAtlasCallback, IMTData.get() );
  777. if (FAILED(hr))
  778. {
  779. IMTData.reset();
  780. wprintf(L"WARNING: Failed to compute IMT from channel %ls (%08X)\n", szChannel, hr );
  781. }
  782. }
  783. }
  784. }
  785. else
  786. {
  787. wprintf(L"\n");
  788. }
  789. // Perform UVAtlas isocharting
  790. wprintf( L"Computing isochart atlas on mesh...\n" );
  791. std::vector<UVAtlasVertex> vb;
  792. std::vector<uint8_t> ib;
  793. float outStretch = 0.f;
  794. size_t outCharts = 0;
  795. std::vector<uint32_t> facePartitioning;
  796. std::vector<uint32_t> vertexRemapArray;
  797. hr = UVAtlasCreate( inMesh->GetPositionBuffer(), nVerts,
  798. inMesh->GetIndexBuffer(), DXGI_FORMAT_R32_UINT, nFaces,
  799. maxCharts, maxStretch, width, height, gutter,
  800. inMesh->GetAdjacencyBuffer(), nullptr,
  801. IMTData.get(),
  802. UVAtlasCallback, UVATLAS_DEFAULT_CALLBACK_FREQUENCY,
  803. uvOptions, vb, ib,
  804. &facePartitioning,
  805. &vertexRemapArray,
  806. &outStretch, &outCharts );
  807. if ( FAILED(hr) )
  808. {
  809. if ( hr == HRESULT_FROM_WIN32( ERROR_INVALID_DATA ) )
  810. {
  811. wprintf( L"\nERROR: Non-manifold mesh\n" );
  812. return 1;
  813. }
  814. else
  815. {
  816. wprintf( L"\nERROR: Failed creating isocharts (%08X)\n", hr );
  817. return 1;
  818. }
  819. }
  820. wprintf( L"Output # of charts: %Iu, resulting stretching %f, %Iu verts\n", outCharts, outStretch, vb.size());
  821. assert((ib.size() / sizeof(uint32_t) ) == (nFaces*3));
  822. assert(facePartitioning.size() == nFaces);
  823. assert(vertexRemapArray.size() == vb.size());
  824. hr = inMesh->UpdateFaces( nFaces, reinterpret_cast<const uint32_t*>( &ib.front() ) );
  825. if ( FAILED(hr) )
  826. {
  827. wprintf(L"\nERROR: Failed applying atlas indices (%08X)\n", hr);
  828. return 1;
  829. }
  830. hr = inMesh->VertexRemap( &vertexRemapArray.front(), vertexRemapArray.size() );
  831. if ( FAILED(hr) )
  832. {
  833. wprintf(L"\nERROR: Failed applying atlas vertex remap (%08X)\n", hr);
  834. return 1;
  835. }
  836. nVerts = vb.size();
  837. #ifdef _DEBUG
  838. std::wstring msgs;
  839. hr = inMesh->Validate( VALIDATE_DEFAULT, &msgs );
  840. if (!msgs.empty())
  841. {
  842. wprintf(L"\nWARNING: \n");
  843. wprintf(msgs.c_str());
  844. }
  845. #endif
  846. // Copy isochart UVs into mesh
  847. {
  848. std::unique_ptr<XMFLOAT2[]> texcoord( new (std::nothrow) XMFLOAT2[nVerts] );
  849. if (!texcoord)
  850. {
  851. wprintf(L"\nERROR: out of memory\n");
  852. return 1;
  853. }
  854. auto txptr = texcoord.get();
  855. size_t j = 0;
  856. for (auto it = vb.cbegin(); it != vb.cend() && j < nVerts; ++it, ++txptr)
  857. {
  858. *txptr = it->uv;
  859. }
  860. hr = inMesh->UpdateUVs( nVerts, texcoord.get() );
  861. if (FAILED(hr))
  862. {
  863. wprintf(L"\nERROR: Failed to update with isochart UVs\n");
  864. return 1;
  865. }
  866. }
  867. if (dwOptions & (1 << OPT_COLOR_MESH))
  868. {
  869. inMaterial.clear();
  870. inMaterial.reserve( _countof(g_ColorList) );
  871. for( size_t j = 0; j < _countof(g_ColorList) && (j < outCharts); ++j )
  872. {
  873. Mesh::Material mtl;
  874. memset( &mtl, 0, sizeof(mtl) );
  875. WCHAR matname[32];
  876. wsprintf( matname, L"Chart%02Iu", j+1 );
  877. mtl.name = matname;
  878. mtl.specularPower = 1.f;
  879. mtl.alpha = 1.f;
  880. XMVECTOR v = XMLoadFloat3( &g_ColorList[j] );
  881. XMStoreFloat3( &mtl.diffuseColor, v );
  882. v = XMVectorScale( v, 0.2f );
  883. XMStoreFloat3( &mtl.ambientColor, v );
  884. inMaterial.push_back(mtl);
  885. }
  886. std::unique_ptr<uint32_t[]> attr( new (std::nothrow) uint32_t[ nFaces ] );
  887. if ( !attr )
  888. {
  889. wprintf(L"\nERROR: out of memory\n" );
  890. return 1;
  891. }
  892. size_t j = 0;
  893. for( auto it = facePartitioning.cbegin(); it != facePartitioning.cend(); ++it, ++j )
  894. {
  895. attr[j] = *it % _countof(g_ColorList);
  896. }
  897. hr = inMesh->UpdateAttributes( nFaces, attr.get() );
  898. if ( FAILED(hr) )
  899. {
  900. wprintf(L"\nERROR: Failed applying atlas attributes (%08X)\n", hr);
  901. return 1;
  902. }
  903. }
  904. if (dwOptions & ((1 << OPT_FLIP) | (1 << OPT_FLIPTC)))
  905. {
  906. hr = inMesh->ReverseWinding( (dwOptions & (1 << OPT_FLIPTC)) ? true : false );
  907. if (FAILED(hr))
  908. {
  909. wprintf(L"\nERROR: Failed reversing winding (%08X)\n", hr);
  910. return 1;
  911. }
  912. }
  913. // Write results
  914. wprintf(L"\n\t->\n");
  915. WCHAR outputPath[ MAX_PATH ] = { 0 };
  916. WCHAR outputExt[ _MAX_EXT] = { 0 };
  917. if ( *szOutputFile )
  918. {
  919. wcscpy_s( outputPath, szOutputFile );
  920. _wsplitpath_s( szOutputFile, nullptr, 0, nullptr, 0, nullptr, 0, outputExt, _MAX_EXT );
  921. }
  922. else
  923. {
  924. if (dwOptions & (1 << OPT_VBO))
  925. {
  926. wcscpy_s(outputExt, L".vbo");
  927. }
  928. else if (dwOptions & (1 << OPT_CMO))
  929. {
  930. wcscpy_s(outputExt, L".cmo");
  931. }
  932. else
  933. {
  934. wcscpy_s(outputExt, L".sdkmesh");
  935. }
  936. WCHAR outFilename[ _MAX_FNAME ] = { 0 };
  937. wcscpy_s( outFilename, fname );
  938. _wmakepath_s( outputPath, nullptr, nullptr, outFilename, outputExt );
  939. }
  940. if ( ~dwOptions & (1 << OPT_OVERWRITE) )
  941. {
  942. if (GetFileAttributesW(outputPath) != INVALID_FILE_ATTRIBUTES)
  943. {
  944. wprintf(L"\nERROR: Output file already exists, use -y to overwrite:\n'%ls'\n", outputPath);
  945. return 1;
  946. }
  947. }
  948. if ( !_wcsicmp(outputExt, L".vbo") )
  949. {
  950. if (!inMesh->GetNormalBuffer() || !inMesh->GetTexCoordBuffer())
  951. {
  952. wprintf( L"\nERROR: VBO requires position, normal, and texcoord\n" );
  953. return 1;
  954. }
  955. if (!inMesh->Is16BitIndexBuffer())
  956. {
  957. wprintf(L"\nERROR: VBO only supports 16-bit indices\n");
  958. return 1;
  959. }
  960. hr = inMesh->ExportToVBO(outputPath);
  961. }
  962. else if ( !_wcsicmp(outputExt, L".sdkmesh") )
  963. {
  964. hr = inMesh->ExportToSDKMESH(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : &inMaterial.front());
  965. }
  966. else if ( !_wcsicmp(outputExt, L".cmo") )
  967. {
  968. if (!inMesh->GetNormalBuffer() || !inMesh->GetTexCoordBuffer() || !inMesh->GetTangentBuffer())
  969. {
  970. wprintf(L"\nERROR: Visual Studio CMO requires position, normal, tangents, and texcoord\n");
  971. return 1;
  972. }
  973. if (!inMesh->Is16BitIndexBuffer())
  974. {
  975. wprintf(L"\nERROR: Visual Studio CMO only supports 16-bit indices\n");
  976. return 1;
  977. }
  978. hr = inMesh->ExportToCMO(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : &inMaterial.front());
  979. }
  980. else if ( !_wcsicmp(outputExt, L".x") )
  981. {
  982. wprintf(L"\nERROR: Legacy Microsoft X files not supported\n");
  983. return 1;
  984. }
  985. else
  986. {
  987. wprintf(L"\nERROR: Unknown output file type '%ls'\n", outputExt);
  988. return 1;
  989. }
  990. if (FAILED(hr))
  991. {
  992. wprintf(L"\nERROR: Failed write (%08X):-> '%ls'\n", hr, outputPath);
  993. return 1;
  994. }
  995. wprintf(L" %Iu vertices, %Iu faces written:\n'%ls'\n", nVerts, nFaces, outputPath);
  996. // Write out UV mesh visualization
  997. if (dwOptions & (1 << OPT_UV_MESH))
  998. {
  999. hr = inMesh->VisualizeUVs();
  1000. if (FAILED(hr))
  1001. {
  1002. wprintf(L"\nERROR: Failed to create UV visualization mesh\n");
  1003. return 1;
  1004. }
  1005. WCHAR uvFilename[_MAX_FNAME] = { 0 };
  1006. wcscpy_s(uvFilename, fname);
  1007. wcscat_s(uvFilename, L"_texture");
  1008. _wmakepath_s(outputPath, nullptr, nullptr, uvFilename, outputExt);
  1009. if (!_wcsicmp(outputExt, L".vbo"))
  1010. {
  1011. hr = inMesh->ExportToVBO(outputPath);
  1012. }
  1013. else if (!_wcsicmp(outputExt, L".sdkmesh"))
  1014. {
  1015. hr = inMesh->ExportToSDKMESH(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : &inMaterial.front());
  1016. }
  1017. else if (!_wcsicmp(outputExt, L".cmo"))
  1018. {
  1019. hr = inMesh->ExportToCMO(outputPath, inMaterial.size(), inMaterial.empty() ? nullptr : &inMaterial.front());
  1020. }
  1021. if (FAILED(hr))
  1022. {
  1023. wprintf(L"\nERROR: Failed uv mesh write (%08X):-> '%ls'\n", hr, outputPath);
  1024. return 1;
  1025. }
  1026. wprintf(L"uv mesh visualization '%ls'\n", outputPath);
  1027. }
  1028. }
  1029. return 0;
  1030. }