BabylonExporter.GLTFExporter.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. using BabylonExport.Entities;
  2. using GLTFExport.Entities;
  3. using Newtonsoft.Json;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Drawing;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Text;
  10. using Color = System.Drawing.Color;
  11. namespace Max2Babylon
  12. {
  13. internal partial class BabylonExporter
  14. {
  15. List<BabylonMaterial> babylonMaterialsToExport;
  16. private List<BabylonNode> babylonNodes;
  17. public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary, bool exportGltfImagesAsBinary)
  18. {
  19. RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
  20. RaiseMessage("GLTFExporter | Exportation started", Color.Blue);
  21. float progressionStep;
  22. var progression = 0.0f;
  23. ReportProgressChanged((int)progression);
  24. // Initialization
  25. initBabylonNodes(babylonScene);
  26. babylonMaterialsToExport = new List<BabylonMaterial>();
  27. var gltf = new GLTF(outputFile);
  28. // Asset
  29. gltf.asset = new GLTFAsset
  30. {
  31. version = "2.0",
  32. generator = "Babylon2Gltf2017",
  33. copyright = "2017 (c) BabylonJS"
  34. // no minVersion
  35. };
  36. // Scene
  37. gltf.scene = 0;
  38. // Scenes
  39. GLTFScene scene = new GLTFScene();
  40. GLTFScene[] scenes = { scene };
  41. gltf.scenes = scenes;
  42. // Meshes
  43. RaiseMessage("GLTFExporter | Exporting meshes");
  44. progression = 10.0f;
  45. ReportProgressChanged((int)progression);
  46. progressionStep = 40.0f / babylonScene.meshes.Length;
  47. foreach (var babylonMesh in babylonScene.meshes)
  48. {
  49. ExportMesh(babylonMesh, gltf, babylonScene);
  50. progression += progressionStep;
  51. ReportProgressChanged((int)progression);
  52. CheckCancelled();
  53. }
  54. // Root nodes
  55. RaiseMessage("GLTFExporter | Exporting nodes");
  56. List<BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
  57. progressionStep = 40.0f / babylonRootNodes.Count;
  58. babylonRootNodes.ForEach(babylonNode =>
  59. {
  60. exportNodeRec(babylonNode, gltf, babylonScene);
  61. progression += progressionStep;
  62. ReportProgressChanged((int)progression);
  63. CheckCancelled();
  64. });
  65. // TODO - Choose between this method and the reverse of X axis
  66. // Switch from left to right handed coordinate system
  67. RaiseMessage("GLTFExporter | Exporting root node");
  68. var tmpNodesList = new List<int>(scene.NodesList);
  69. var rootNode = new BabylonMesh
  70. {
  71. name = "root",
  72. rotation = new float[] { 0, (float)Math.PI, 0 },
  73. scaling = new float[] { 1, 1, -1 },
  74. idGroupInstance = -1
  75. };
  76. scene.NodesList.Clear();
  77. GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);
  78. gltfRootNode.ChildrenList.AddRange(tmpNodesList);
  79. // Materials
  80. RaiseMessage("GLTFExporter | Exporting materials");
  81. foreach (var babylonMaterial in babylonMaterialsToExport)
  82. {
  83. ExportMaterial(babylonMaterial, gltf);
  84. CheckCancelled();
  85. };
  86. RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
  87. if (exportGltfImagesAsBinary)
  88. {
  89. SwitchImagesFromUriToBinary(gltf);
  90. }
  91. // Cast lists to arrays
  92. gltf.Prepare();
  93. // Output
  94. RaiseMessage("GLTFExporter | Saving to output file");
  95. string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
  96. File.WriteAllText(outputGltfFile, gltfToJson(gltf));
  97. // Write data to binary file
  98. string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
  99. using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
  100. {
  101. gltf.BuffersList.ForEach(buffer =>
  102. {
  103. buffer.bytesList = new List<byte>();
  104. gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
  105. {
  106. bufferView.bytesList.ForEach(b => writer.Write(b));
  107. buffer.bytesList.AddRange(bufferView.bytesList);
  108. });
  109. });
  110. }
  111. // Binary
  112. if (generateBinary)
  113. {
  114. // Export glTF data to binary format .glb
  115. RaiseMessage("GLTFExporter | Generating .glb file");
  116. // Header
  117. UInt32 magic = 0x46546C67; // ASCII code for glTF
  118. UInt32 version = 2;
  119. UInt32 length = 12; // Header length
  120. // --- JSON chunk ---
  121. UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
  122. // Remove buffers uri
  123. foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
  124. {
  125. gltfBuffer.uri = null;
  126. }
  127. // Switch images to binary if not already done
  128. // TODO - make it optional
  129. if (!exportGltfImagesAsBinary)
  130. {
  131. var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
  132. imageBufferViews.ForEach(imageBufferView =>
  133. {
  134. imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
  135. });
  136. }
  137. gltf.Prepare();
  138. // Serialize gltf data to JSON string then convert it to bytes
  139. byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
  140. // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
  141. var nbSpaceToAdd = chunkDataJson.Length % 4 == 0 ? 0 : (4 - chunkDataJson.Length % 4);
  142. var chunkDataJsonList = new List<byte>(chunkDataJson);
  143. for (int i = 0; i < nbSpaceToAdd; i++)
  144. {
  145. chunkDataJsonList.Add(0x20);
  146. }
  147. chunkDataJson = chunkDataJsonList.ToArray();
  148. UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
  149. length += chunkLengthJson + 8; // 8 = JSON chunk header length
  150. // bin chunk
  151. UInt32 chunkTypeBin = 0x004E4942; // ASCII code for BIN
  152. UInt32 chunkLengthBin = 0;
  153. if (gltf.BuffersList.Count > 0)
  154. {
  155. foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
  156. {
  157. chunkLengthBin += (uint)gltfBuffer.byteLength;
  158. }
  159. length += chunkLengthBin + 8; // 8 = bin chunk header length
  160. }
  161. // Write binary file
  162. string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
  163. using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
  164. {
  165. // Header
  166. writer.Write(magic);
  167. writer.Write(version);
  168. writer.Write(length);
  169. // JSON chunk
  170. writer.Write(chunkLengthJson);
  171. writer.Write(chunkTypeJson);
  172. writer.Write(chunkDataJson);
  173. // bin chunk
  174. if (gltf.BuffersList.Count > 0)
  175. {
  176. writer.Write(chunkLengthBin);
  177. writer.Write(chunkTypeBin);
  178. gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
  179. }
  180. };
  181. }
  182. ReportProgressChanged(100);
  183. }
  184. private List<BabylonNode> initBabylonNodes(BabylonScene babylonScene)
  185. {
  186. babylonNodes = new List<BabylonNode>();
  187. if (babylonScene.meshes != null)
  188. {
  189. int idGroupInstance = 0;
  190. foreach (var babylonMesh in babylonScene.meshes)
  191. {
  192. var babylonAbstractMeshes = new List<BabylonAbstractMesh>();
  193. babylonAbstractMeshes.Add(babylonMesh);
  194. if (babylonMesh.instances != null)
  195. {
  196. babylonAbstractMeshes.AddRange(babylonMesh.instances);
  197. }
  198. // Add mesh and instances to node list
  199. babylonNodes.AddRange(babylonAbstractMeshes);
  200. // Tag mesh and instances with an identifier
  201. babylonAbstractMeshes.ForEach(babylonAbstractMesh => babylonAbstractMesh.idGroupInstance = idGroupInstance);
  202. idGroupInstance++;
  203. }
  204. }
  205. if (babylonScene.lights != null)
  206. {
  207. babylonNodes.AddRange(babylonScene.lights);
  208. }
  209. if (babylonScene.cameras != null)
  210. {
  211. babylonNodes.AddRange(babylonScene.cameras);
  212. }
  213. return babylonNodes;
  214. }
  215. private void exportNodeRec(BabylonNode babylonNode, GLTF gltf, BabylonScene babylonScene, GLTFNode gltfParentNode = null)
  216. {
  217. GLTFNode gltfNode = null;
  218. var type = babylonNode.GetType();
  219. if (type == typeof(BabylonAbstractMesh) ||
  220. type.IsSubclassOf(typeof(BabylonAbstractMesh)))
  221. {
  222. gltfNode = ExportAbstractMesh(babylonNode as BabylonAbstractMesh, gltf, gltfParentNode);
  223. }
  224. else if (type == typeof(BabylonCamera))
  225. {
  226. GLTFCamera gltfCamera = ExportCamera(babylonNode as BabylonCamera, gltf, gltfParentNode);
  227. gltfNode = gltfCamera.gltfNode;
  228. }
  229. else if (type == typeof(BabylonLight))
  230. {
  231. if (isNodeRelevantToExport(babylonNode, babylonScene))
  232. {
  233. // Export light nodes as empty nodes (no lights in glTF 2.0 core)
  234. RaiseWarning($"GLTFExporter | Light named {babylonNode.name} has children but lights are not exported with glTF 2.0 core version. An empty node is used instead.", 1);
  235. gltfNode = ExportLight(babylonNode as BabylonLight, gltf, gltfParentNode);
  236. }
  237. else
  238. {
  239. RaiseMessage($"GLTFExporter | Light named {babylonNode.name} is not relevant to export", 1);
  240. }
  241. }
  242. else
  243. {
  244. RaiseError($"Node named {babylonNode.name} as no exporter", 1);
  245. }
  246. CheckCancelled();
  247. // If node is exported successfully...
  248. if (gltfNode != null)
  249. {
  250. // ...export its children
  251. List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
  252. babylonDescendants.ForEach(descendant => exportNodeRec(descendant, gltf, babylonScene, gltfNode));
  253. }
  254. }
  255. private List<BabylonNode> getDescendants(BabylonNode babylonNode, BabylonScene babylonScene)
  256. {
  257. return babylonNodes.FindAll(node => node.parentId == babylonNode.id);
  258. }
  259. /// <summary>
  260. /// Return true if node descendant hierarchy has any Mesh or Camera to export
  261. /// </summary>
  262. private bool isNodeRelevantToExport(BabylonNode babylonNode, BabylonScene babylonScene)
  263. {
  264. var type = babylonNode.GetType();
  265. if (type == typeof(BabylonAbstractMesh) ||
  266. type.IsSubclassOf(typeof(BabylonAbstractMesh)) ||
  267. type == typeof(BabylonCamera))
  268. {
  269. return true;
  270. }
  271. // Descandant recursivity
  272. List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
  273. int indexDescendant = 0;
  274. while (indexDescendant < babylonDescendants.Count) // while instead of for to stop as soon as a relevant node has been found
  275. {
  276. if (isNodeRelevantToExport(babylonDescendants[indexDescendant], babylonScene))
  277. {
  278. return true;
  279. }
  280. indexDescendant++;
  281. }
  282. // No relevant node found in hierarchy
  283. return false;
  284. }
  285. private string gltfToJson(GLTF gltf)
  286. {
  287. var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings());
  288. var sb = new StringBuilder();
  289. var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
  290. // Do not use the optimized writer because it's not necessary to truncate values
  291. // Use the bounded writer in case some values are infinity ()
  292. using (var jsonWriter = new JsonTextWriterBounded(sw))
  293. {
  294. jsonWriter.Formatting = Formatting.None;
  295. jsonSerializer.Serialize(jsonWriter, gltf);
  296. }
  297. return sb.ToString();
  298. }
  299. private List<GLTFBufferView> SwitchImagesFromUriToBinary(GLTF gltf)
  300. {
  301. var imageBufferViews = new List<GLTFBufferView>();
  302. foreach (GLTFImage gltfImage in gltf.ImagesList)
  303. {
  304. var path = Path.Combine(gltf.OutputFolder, gltfImage.uri);
  305. using (Image image = Image.FromFile(path))
  306. {
  307. using (MemoryStream m = new MemoryStream())
  308. {
  309. var imageFormat = gltfImage.FileExtension == "jpeg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png;
  310. image.Save(m, imageFormat);
  311. byte[] imageBytes = m.ToArray();
  312. // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
  313. var nbSpaceToAdd = imageBytes.Length % 4 == 0 ? 0 : (4 - imageBytes.Length % 4);
  314. var imageBytesList = new List<byte>(imageBytes);
  315. for (int i = 0; i < nbSpaceToAdd; i++)
  316. {
  317. imageBytesList.Add(0x00);
  318. }
  319. imageBytes = imageBytesList.ToArray();
  320. // BufferView - Image
  321. var buffer = gltf.buffer;
  322. var bufferViewImage = new GLTFBufferView
  323. {
  324. name = "bufferViewImage",
  325. buffer = buffer.index,
  326. Buffer = buffer,
  327. byteOffset = buffer.byteLength
  328. };
  329. bufferViewImage.index = gltf.BufferViewsList.Count;
  330. gltf.BufferViewsList.Add(bufferViewImage);
  331. imageBufferViews.Add(bufferViewImage);
  332. gltfImage.uri = null;
  333. gltfImage.bufferView = bufferViewImage.index;
  334. gltfImage.mimeType = "image/" + gltfImage.FileExtension;
  335. bufferViewImage.bytesList.AddRange(imageBytes);
  336. bufferViewImage.byteLength += imageBytes.Length;
  337. bufferViewImage.Buffer.byteLength += imageBytes.Length;
  338. }
  339. }
  340. }
  341. return imageBufferViews;
  342. }
  343. }
  344. }