BabylonExporter.Texture.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using Autodesk.Max;
  5. using BabylonExport.Entities;
  6. using System.Drawing;
  7. namespace Max2Babylon
  8. {
  9. partial class BabylonExporter
  10. {
  11. // -------------------------------
  12. // --- "public" export methods ---
  13. // -------------------------------
  14. private BabylonTexture ExportTexture(IStdMat2 stdMat, int index, out BabylonFresnelParameters fresnelParameters, BabylonScene babylonScene, bool allowCube = false, bool forceAlpha = false)
  15. {
  16. fresnelParameters = null;
  17. if (!stdMat.MapEnabled(index))
  18. {
  19. return null;
  20. }
  21. var texMap = stdMat.GetSubTexmap(index);
  22. if (texMap == null)
  23. {
  24. RaiseWarning("Texture channel " + index + " activated but no texture found.", 2);
  25. return null;
  26. }
  27. texMap = _exportFresnelParameters(texMap, out fresnelParameters);
  28. var amount = stdMat.GetTexmapAmt(index, 0);
  29. return _exportTexture(texMap, amount, babylonScene, allowCube, forceAlpha);
  30. }
  31. private BabylonTexture ExportPBRTexture(IIGameMaterial materialNode, int index, BabylonScene babylonScene, float amount = 1.0f)
  32. {
  33. var texMap = _getTexMap(materialNode, index);
  34. if (texMap != null)
  35. {
  36. return _exportTexture(texMap, amount, babylonScene);
  37. }
  38. return null;
  39. }
  40. private BabylonTexture ExportMetallicRoughnessTexture(IIGameMaterial materialNode, float metallic, float roughness, BabylonScene babylonScene, string materialName)
  41. {
  42. ITexmap metallicTexMap = _getTexMap(materialNode, 5);
  43. ITexmap roughnessTexMap = _getTexMap(materialNode, 4);
  44. if (metallicTexMap == null && roughnessTexMap == null)
  45. {
  46. return null;
  47. }
  48. // Use one as a reference for UVs parameters
  49. var referenceTexMap = metallicTexMap != null ? metallicTexMap : roughnessTexMap;
  50. // --- Babylon texture ---
  51. if (referenceTexMap.GetParamBlock(0) == null || referenceTexMap.GetParamBlock(0).Owner == null)
  52. {
  53. return null;
  54. }
  55. var texture = referenceTexMap.GetParamBlock(0).Owner as IBitmapTex;
  56. if (texture == null)
  57. {
  58. return null;
  59. }
  60. var babylonTexture = new BabylonTexture
  61. {
  62. name = materialName + "_metallicRoughness" + ".jpg" // TODO - unsafe name, may conflict with another texture name
  63. };
  64. // Level
  65. babylonTexture.level = 1.0f;
  66. // No alpha
  67. babylonTexture.hasAlpha = false;
  68. babylonTexture.getAlphaFromRGB = false;
  69. // UVs
  70. var uvGen = _exportUV(texture, babylonTexture);
  71. // Is cube
  72. _exportIsCube(texture, babylonTexture, false);
  73. // --- Merge metallic and roughness maps ---
  74. // Load bitmaps
  75. var metallicBitmap = _loadTexture(metallicTexMap);
  76. var roughnessBitmap = _loadTexture(roughnessTexMap);
  77. // Retreive dimensions
  78. int width = 0;
  79. int height = 0;
  80. var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, metallicBitmap, roughnessBitmap);
  81. if (!haveSameDimensions)
  82. {
  83. RaiseWarning("Metallic and roughness maps should have same dimensions", 2);
  84. }
  85. // Create metallic+roughness map
  86. Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
  87. for (int x = 0; x < width; x++)
  88. {
  89. for (int y = 0; y < height; y++)
  90. {
  91. var _metallic = metallicBitmap != null ? metallicBitmap.GetPixel(x, y).R :
  92. metallic * 255.0f;
  93. var _roughness = roughnessBitmap != null ? roughnessBitmap.GetPixel(x, y).R :
  94. roughness * 255.0f;
  95. // The metalness values are sampled from the B channel.
  96. // The roughness values are sampled from the G channel.
  97. // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
  98. Color colorMetallicRoughness = Color.FromArgb(
  99. 0,
  100. (int)_roughness,
  101. (int)_metallic
  102. );
  103. metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
  104. }
  105. }
  106. // Write bitmap
  107. var absolutePath = Path.Combine(babylonScene.OutputPath, babylonTexture.name);
  108. RaiseMessage($"Texture | write image '{babylonTexture.name}'", 2);
  109. metallicRoughnessBitmap.Save(absolutePath);
  110. return babylonTexture;
  111. }
  112. // -------------------------
  113. // -- Export sub methods ---
  114. // -------------------------
  115. private BabylonTexture _exportTexture(ITexmap texMap, float amount, BabylonScene babylonScene, bool allowCube = false, bool forceAlpha = false)
  116. {
  117. if (texMap.GetParamBlock(0) == null || texMap.GetParamBlock(0).Owner == null)
  118. {
  119. return null;
  120. }
  121. var texture = texMap.GetParamBlock(0).Owner as IBitmapTex;
  122. if (texture == null)
  123. {
  124. return null;
  125. }
  126. var babylonTexture = new BabylonTexture
  127. {
  128. name = Path.GetFileName(texture.MapName)
  129. };
  130. // Level
  131. babylonTexture.level = amount;
  132. // Alpha
  133. if (forceAlpha)
  134. {
  135. babylonTexture.hasAlpha = true;
  136. babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2) || (texture.AlphaSource == 3);
  137. }
  138. else
  139. {
  140. babylonTexture.hasAlpha = (texture.AlphaSource != 3);
  141. babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2);
  142. }
  143. // UVs
  144. var uvGen = _exportUV(texture, babylonTexture);
  145. // Animations
  146. var animations = new List<BabylonAnimation>();
  147. ExportFloatAnimation("uOffset", animations, key => new[] { uvGen.GetUOffs(key) });
  148. ExportFloatAnimation("vOffset", animations, key => new[] { -uvGen.GetVOffs(key) });
  149. ExportFloatAnimation("uScale", animations, key => new[] { uvGen.GetUScl(key) });
  150. ExportFloatAnimation("vScale", animations, key => new[] { uvGen.GetVScl(key) });
  151. ExportFloatAnimation("uAng", animations, key => new[] { uvGen.GetUAng(key) });
  152. ExportFloatAnimation("vAng", animations, key => new[] { uvGen.GetVAng(key) });
  153. ExportFloatAnimation("wAng", animations, key => new[] { uvGen.GetWAng(key) });
  154. babylonTexture.animations = animations.ToArray();
  155. // Is cube
  156. _exportIsCube(texture, babylonTexture, allowCube);
  157. // Copy texture to output
  158. var absolutePath = texture.Map.FullFilePath;
  159. try
  160. {
  161. if (File.Exists(absolutePath))
  162. {
  163. if (CopyTexturesToOutput)
  164. {
  165. File.Copy(absolutePath, Path.Combine(babylonScene.OutputPath, babylonTexture.name), true);
  166. }
  167. }
  168. }
  169. catch
  170. {
  171. // silently fails
  172. }
  173. return babylonTexture;
  174. }
  175. private ITexmap _exportFresnelParameters(ITexmap texMap, out BabylonFresnelParameters fresnelParameters)
  176. {
  177. fresnelParameters = null;
  178. // Fallout
  179. if (texMap.ClassName == "Falloff") // This is the only way I found to detect it. This is crappy but it works
  180. {
  181. RaiseMessage("fresnelParameters", 2);
  182. fresnelParameters = new BabylonFresnelParameters();
  183. var paramBlock = texMap.GetParamBlock(0);
  184. var color1 = paramBlock.GetColor(0, 0, 0);
  185. var color2 = paramBlock.GetColor(4, 0, 0);
  186. fresnelParameters.isEnabled = true;
  187. fresnelParameters.leftColor = color2.ToArray();
  188. fresnelParameters.rightColor = color1.ToArray();
  189. if (paramBlock.GetInt(8, 0, 0) == 2)
  190. {
  191. fresnelParameters.power = paramBlock.GetFloat(12, 0, 0);
  192. }
  193. else
  194. {
  195. fresnelParameters.power = 1;
  196. }
  197. var texMap1 = paramBlock.GetTexmap(2, 0, 0);
  198. var texMap1On = paramBlock.GetInt(3, 0, 0);
  199. var texMap2 = paramBlock.GetTexmap(6, 0, 0);
  200. var texMap2On = paramBlock.GetInt(7, 0, 0);
  201. if (texMap1 != null && texMap1On != 0)
  202. {
  203. texMap = texMap1;
  204. fresnelParameters.rightColor = new float[] { 1, 1, 1 };
  205. if (texMap2 != null && texMap2On != 0)
  206. {
  207. RaiseWarning(string.Format("You cannot specify two textures for falloff. Only one is supported"), 2);
  208. }
  209. }
  210. else if (texMap2 != null && texMap2On != 0)
  211. {
  212. fresnelParameters.leftColor = new float[] { 1, 1, 1 };
  213. texMap = texMap2;
  214. }
  215. else
  216. {
  217. return null;
  218. }
  219. }
  220. return texMap;
  221. }
  222. private IStdUVGen _exportUV(IBitmapTex texture, BabylonTexture babylonTexture)
  223. {
  224. var uvGen = texture.UVGen;
  225. switch (uvGen.GetCoordMapping(0))
  226. {
  227. case 1: //MAP_SPHERICAL
  228. babylonTexture.coordinatesMode = 1;
  229. break;
  230. case 2: //MAP_PLANAR
  231. babylonTexture.coordinatesMode = 2;
  232. break;
  233. default:
  234. babylonTexture.coordinatesMode = 0;
  235. break;
  236. }
  237. babylonTexture.coordinatesIndex = uvGen.MapChannel - 1;
  238. if (uvGen.MapChannel > 2)
  239. {
  240. RaiseWarning(string.Format("Unsupported map channel, Only channel 1 and 2 are supported."), 2);
  241. }
  242. babylonTexture.uOffset = uvGen.GetUOffs(0);
  243. babylonTexture.vOffset = uvGen.GetVOffs(0);
  244. babylonTexture.uScale = uvGen.GetUScl(0);
  245. babylonTexture.vScale = uvGen.GetVScl(0);
  246. if (Path.GetExtension(texture.MapName).ToLower() == ".dds")
  247. {
  248. babylonTexture.vScale *= -1; // Need to invert Y-axis for DDS texture
  249. }
  250. babylonTexture.uAng = uvGen.GetUAng(0);
  251. babylonTexture.vAng = uvGen.GetVAng(0);
  252. babylonTexture.wAng = uvGen.GetWAng(0);
  253. babylonTexture.wrapU = BabylonTexture.AddressMode.CLAMP_ADDRESSMODE; // CLAMP
  254. if ((uvGen.TextureTiling & 1) != 0) // WRAP
  255. {
  256. babylonTexture.wrapU = BabylonTexture.AddressMode.WRAP_ADDRESSMODE;
  257. }
  258. else if ((uvGen.TextureTiling & 4) != 0) // MIRROR
  259. {
  260. babylonTexture.wrapU = BabylonTexture.AddressMode.MIRROR_ADDRESSMODE;
  261. }
  262. babylonTexture.wrapV = BabylonTexture.AddressMode.CLAMP_ADDRESSMODE; // CLAMP
  263. if ((uvGen.TextureTiling & 2) != 0) // WRAP
  264. {
  265. babylonTexture.wrapV = BabylonTexture.AddressMode.WRAP_ADDRESSMODE;
  266. }
  267. else if ((uvGen.TextureTiling & 8) != 0) // MIRROR
  268. {
  269. babylonTexture.wrapV = BabylonTexture.AddressMode.MIRROR_ADDRESSMODE;
  270. }
  271. return uvGen;
  272. }
  273. private void _exportIsCube(IBitmapTex texture, BabylonTexture babylonTexture, bool allowCube)
  274. {
  275. var absolutePath = texture.Map.FullFilePath;
  276. try
  277. {
  278. if (File.Exists(absolutePath))
  279. {
  280. babylonTexture.isCube = _isTextureCube(absolutePath);
  281. }
  282. else
  283. {
  284. RaiseWarning(string.Format("Texture {0} not found.", babylonTexture.name), 2);
  285. }
  286. }
  287. catch
  288. {
  289. // silently fails
  290. }
  291. if (babylonTexture.isCube && !allowCube)
  292. {
  293. RaiseWarning(string.Format("Cube texture are only supported for reflection channel"), 2);
  294. }
  295. }
  296. private bool _isTextureCube(string filepath)
  297. {
  298. try
  299. {
  300. if (Path.GetExtension(filepath).ToLower() != ".dds")
  301. {
  302. return false;
  303. }
  304. var data = File.ReadAllBytes(filepath);
  305. var intArray = new int[data.Length / 4];
  306. Buffer.BlockCopy(data, 0, intArray, 0, intArray.Length * 4);
  307. int width = intArray[4];
  308. int height = intArray[3];
  309. int mipmapsCount = intArray[7];
  310. if ((width >> (mipmapsCount - 1)) > 1)
  311. {
  312. var expected = 1;
  313. var currentSize = Math.Max(width, height);
  314. while (currentSize > 1)
  315. {
  316. currentSize = currentSize >> 1;
  317. expected++;
  318. }
  319. RaiseWarning(string.Format("Mipmaps chain is not complete: {0} maps instead of {1} (based on texture max size: {2})", mipmapsCount, expected, width), 2);
  320. RaiseWarning(string.Format("You must generate a complete mipmaps chain for .dds)"), 2);
  321. RaiseWarning(string.Format("Mipmaps will be disabled for this texture. If you want automatic texture generation you cannot use a .dds)"), 2);
  322. }
  323. bool isCube = (intArray[28] & 0x200) == 0x200;
  324. return isCube;
  325. }
  326. catch
  327. {
  328. return false;
  329. }
  330. }
  331. // -------------------------
  332. // --------- Utils ---------
  333. // -------------------------
  334. private ITexmap _getTexMap(IIGameMaterial materialNode, int index)
  335. {
  336. ITexmap texMap = null;
  337. if (materialNode.MaxMaterial.SubTexmapOn(index) == 1)
  338. {
  339. texMap = materialNode.MaxMaterial.GetSubTexmap(index);
  340. // No warning displayed because by default, physical material in 3ds Max have all maps on
  341. // Would be tedious for the user to uncheck all unused maps
  342. //if (texMap == null)
  343. //{
  344. // RaiseWarning("Texture channel " + index + " activated but no texture found.", 2);
  345. //}
  346. }
  347. return texMap;
  348. }
  349. private bool _getMinimalBitmapDimensions(out int width, out int height, params Bitmap[] bitmaps)
  350. {
  351. var haveSameDimensions = true;
  352. var bitmapsNoNull = ((new List<Bitmap>(bitmaps)).FindAll(bitmap => bitmap != null)).ToArray();
  353. if (bitmapsNoNull.Length > 0)
  354. {
  355. // Init with first element
  356. width = bitmapsNoNull[0].Width;
  357. height = bitmapsNoNull[0].Height;
  358. // Update with others
  359. for (int i = 1; i < bitmapsNoNull.Length; i++)
  360. {
  361. var bitmap = bitmapsNoNull[i];
  362. if (width != bitmap.Width || height != bitmap.Height)
  363. {
  364. haveSameDimensions = false;
  365. }
  366. width = Math.Min(width, bitmap.Width);
  367. height = Math.Min(height, bitmap.Height);
  368. }
  369. }
  370. else
  371. {
  372. width = 0;
  373. height = 0;
  374. }
  375. return haveSameDimensions;
  376. }
  377. private Bitmap LoadTexture(string absolutePath)
  378. {
  379. if (File.Exists(absolutePath))
  380. {
  381. return new Bitmap(absolutePath);
  382. }
  383. else
  384. {
  385. RaiseWarning(string.Format("Texture {0} not found.", Path.GetFileName(absolutePath), 2));
  386. return null;
  387. }
  388. }
  389. private Bitmap _loadTexture(ITexmap texMap)
  390. {
  391. if (texMap == null || texMap.GetParamBlock(0) == null || texMap.GetParamBlock(0).Owner == null)
  392. {
  393. return null;
  394. }
  395. var texture = texMap.GetParamBlock(0).Owner as IBitmapTex;
  396. if (texture == null)
  397. {
  398. return null;
  399. }
  400. return LoadTexture(texture.Map.FullFilePath);
  401. }
  402. }
  403. }