BabylonExporter.GLTFExporter.Mesh.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. using Autodesk.Max;
  2. using BabylonExport.Entities;
  3. using GLTFExport.Entities;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. namespace Max2Babylon
  9. {
  10. partial class BabylonExporter
  11. {
  12. private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
  13. {
  14. RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);
  15. // --------------------------
  16. // --- Mesh from babylon ----
  17. // --------------------------
  18. if (babylonMesh.positions == null)
  19. {
  20. RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
  21. return null;
  22. }
  23. RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
  24. // Retreive general data from babylon mesh
  25. int nbVertices = babylonMesh.positions.Length / 3;
  26. bool hasUV = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0;
  27. bool hasUV2 = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0;
  28. bool hasColor = babylonMesh.colors != null && babylonMesh.colors.Length > 0;
  29. RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3);
  30. RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
  31. RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
  32. RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);
  33. // Retreive vertices data from babylon mesh
  34. List<GLTFGlobalVertex> globalVertices = new List<GLTFGlobalVertex>();
  35. for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
  36. {
  37. GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
  38. globalVertex.Position = createIPoint3(babylonMesh.positions, indexVertex);
  39. // Switch from left to right handed coordinate system
  40. //globalVertex.Position.X *= -1;
  41. globalVertex.Normal = createIPoint3(babylonMesh.normals, indexVertex);
  42. if (hasUV)
  43. {
  44. globalVertex.UV = createIPoint2(babylonMesh.uvs, indexVertex);
  45. // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
  46. // While for Babylon, it corresponds to the lower left corner of a texture image
  47. globalVertex.UV.Y = 1 - globalVertex.UV.Y;
  48. }
  49. if (hasUV2)
  50. {
  51. globalVertex.UV2 = createIPoint2(babylonMesh.uvs2, indexVertex);
  52. // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
  53. // While for Babylon, it corresponds to the lower left corner of a texture image
  54. globalVertex.UV2.Y = 1 - globalVertex.UV2.Y;
  55. }
  56. if (hasColor)
  57. {
  58. globalVertex.Color = createIPoint4(babylonMesh.colors, indexVertex).ToArray();
  59. }
  60. globalVertices.Add(globalVertex);
  61. }
  62. // Retreive indices from babylon mesh
  63. List<ushort> babylonIndices = new List<ushort>();
  64. babylonIndices = babylonMesh.indices.ToList().ConvertAll(new Converter<int, ushort>(n => (ushort)n));
  65. // For triangle primitives in gltf, the front face has a counter-clockwise (CCW) winding order
  66. // Swap face side
  67. //for (int i = 0; i < babylonIndices.Count; i += 3)
  68. //{
  69. // var tmp = babylonIndices[i];
  70. // babylonIndices[i] = babylonIndices[i + 2];
  71. // babylonIndices[i + 2] = tmp;
  72. //}
  73. // --------------------------
  74. // ------- Init glTF --------
  75. // --------------------------
  76. RaiseMessage("GLTFExporter.Mesh | Init glTF", 2);
  77. // Mesh
  78. var gltfMesh = new GLTFMesh { name = babylonMesh.name };
  79. gltfMesh.index = gltf.MeshesList.Count;
  80. gltf.MeshesList.Add(gltfMesh);
  81. gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
  82. // Buffer
  83. var buffer = gltf.buffer;
  84. if (buffer == null)
  85. {
  86. buffer = new GLTFBuffer
  87. {
  88. uri = gltf.OutputFile + ".bin"
  89. };
  90. buffer.index = gltf.BuffersList.Count;
  91. gltf.BuffersList.Add(buffer);
  92. gltf.buffer = buffer;
  93. }
  94. // BufferView - Scalar
  95. var bufferViewScalar = gltf.bufferViewScalar;
  96. if (bufferViewScalar == null)
  97. {
  98. bufferViewScalar = new GLTFBufferView
  99. {
  100. name = "bufferViewScalar",
  101. buffer = buffer.index,
  102. Buffer = buffer
  103. };
  104. bufferViewScalar.index = gltf.BufferViewsList.Count;
  105. gltf.BufferViewsList.Add(bufferViewScalar);
  106. gltf.bufferViewScalar = bufferViewScalar;
  107. }
  108. // BufferView - Vector3
  109. var bufferViewFloatVec3 = gltf.bufferViewFloatVec3;
  110. if (bufferViewFloatVec3 == null)
  111. {
  112. bufferViewFloatVec3 = new GLTFBufferView
  113. {
  114. name = "bufferViewFloatVec3",
  115. buffer = buffer.index,
  116. Buffer = buffer,
  117. byteOffset = 0,
  118. byteStride = 12 // Field only defined for buffer views that contain vertex attributes. A vertex needs 3 * 4 bytes
  119. };
  120. bufferViewFloatVec3.index = gltf.BufferViewsList.Count;
  121. gltf.BufferViewsList.Add(bufferViewFloatVec3);
  122. gltf.bufferViewFloatVec3 = bufferViewFloatVec3;
  123. }
  124. // BufferView - Vector4
  125. GLTFBufferView bufferViewFloatVec4 = null;
  126. if (hasColor)
  127. {
  128. bufferViewFloatVec4 = gltf.bufferViewFloatVec4;
  129. if (bufferViewFloatVec4 == null)
  130. {
  131. bufferViewFloatVec4 = new GLTFBufferView
  132. {
  133. name = "bufferViewFloatVec4",
  134. buffer = buffer.index,
  135. Buffer = buffer,
  136. byteOffset = 0,
  137. byteStride = 16 // Field only defined for buffer views that contain vertex attributes. A vertex needs 4 * 4 bytes
  138. };
  139. bufferViewFloatVec4.index = gltf.BufferViewsList.Count;
  140. gltf.BufferViewsList.Add(bufferViewFloatVec4);
  141. gltf.bufferViewFloatVec4 = bufferViewFloatVec4;
  142. }
  143. }
  144. // BufferView - Vector2
  145. GLTFBufferView bufferViewFloatVec2 = null;
  146. if (hasUV || hasUV2)
  147. {
  148. bufferViewFloatVec2 = gltf.bufferViewFloatVec2;
  149. if (bufferViewFloatVec2 == null)
  150. {
  151. bufferViewFloatVec2 = new GLTFBufferView
  152. {
  153. name = "bufferViewFloatVec2",
  154. buffer = buffer.index,
  155. Buffer = buffer,
  156. byteStride = 8 // Field only defined for buffer views that contain vertex attributes. A vertex needs 2 * 4 bytes
  157. };
  158. bufferViewFloatVec2.index = gltf.BufferViewsList.Count;
  159. gltf.BufferViewsList.Add(bufferViewFloatVec2);
  160. gltf.bufferViewFloatVec2 = bufferViewFloatVec2;
  161. }
  162. }
  163. // --------------------------
  164. // ---- glTF primitives -----
  165. // --------------------------
  166. RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
  167. var meshPrimitives = new List<GLTFMeshPrimitive>();
  168. // Global vertices are sorted per submesh
  169. var globalVerticesSubMeshes = new List<List<GLTFGlobalVertex>>();
  170. // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
  171. // Thus, the gltf indices list is a concatenation of sub lists all 0-based
  172. // Example for 2 triangles, each being a submesh:
  173. // babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
  174. var gltfIndices = new List<ushort>();
  175. foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
  176. {
  177. // --------------------------
  178. // ------ SubMesh data ------
  179. // --------------------------
  180. List<GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);
  181. globalVerticesSubMeshes.Add(globalVerticesSubMesh);
  182. List<ushort> _indices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
  183. // Indices of this submesh / primitive are updated to be 0-based
  184. var minIndiceValue = _indices.Min(); // Should be equal to babylonSubMesh.indexStart
  185. for (int indexIndice = 0; indexIndice < _indices.Count; indexIndice++)
  186. {
  187. _indices[indexIndice] -= minIndiceValue;
  188. }
  189. gltfIndices.AddRange(_indices);
  190. // --------------------------
  191. // -- Init glTF primitive ---
  192. // --------------------------
  193. // MeshPrimitive
  194. var meshPrimitive = new GLTFMeshPrimitive
  195. {
  196. attributes = new Dictionary<string, int>()
  197. };
  198. meshPrimitives.Add(meshPrimitive);
  199. // Accessor - Indices
  200. var accessorIndices = new GLTFAccessor
  201. {
  202. name = "accessorIndices",
  203. bufferView = bufferViewScalar.index,
  204. BufferView = bufferViewScalar,
  205. componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT,
  206. type = GLTFAccessor.TypeEnum.SCALAR.ToString()
  207. };
  208. accessorIndices.index = gltf.AccessorsList.Count;
  209. gltf.AccessorsList.Add(accessorIndices);
  210. meshPrimitive.indices = accessorIndices.index;
  211. // Accessor - Positions
  212. var accessorPositions = new GLTFAccessor
  213. {
  214. name = "accessorPositions",
  215. bufferView = bufferViewFloatVec3.index,
  216. BufferView = bufferViewFloatVec3,
  217. componentType = GLTFAccessor.ComponentType.FLOAT,
  218. type = GLTFAccessor.TypeEnum.VEC3.ToString(),
  219. min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue },
  220. max = new float[] { float.MinValue, float.MinValue, float.MinValue }
  221. };
  222. accessorPositions.index = gltf.AccessorsList.Count;
  223. gltf.AccessorsList.Add(accessorPositions);
  224. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
  225. // Accessor - Normals
  226. var accessorNormals = new GLTFAccessor
  227. {
  228. name = "accessorNormals",
  229. bufferView = bufferViewFloatVec3.index,
  230. BufferView = bufferViewFloatVec3,
  231. componentType = GLTFAccessor.ComponentType.FLOAT,
  232. type = GLTFAccessor.TypeEnum.VEC3.ToString()
  233. };
  234. accessorNormals.index = gltf.AccessorsList.Count;
  235. gltf.AccessorsList.Add(accessorNormals);
  236. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
  237. // Accessor - Colors
  238. GLTFAccessor accessorColors = null;
  239. if (hasColor)
  240. {
  241. accessorColors = new GLTFAccessor
  242. {
  243. name = "accessorColors",
  244. bufferView = bufferViewFloatVec4.index,
  245. BufferView = bufferViewFloatVec4,
  246. componentType = GLTFAccessor.ComponentType.FLOAT,
  247. type = GLTFAccessor.TypeEnum.VEC4.ToString()
  248. };
  249. accessorColors.index = gltf.AccessorsList.Count;
  250. gltf.AccessorsList.Add(accessorColors);
  251. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
  252. }
  253. // Accessor - UV
  254. GLTFAccessor accessorUVs = null;
  255. if (hasUV)
  256. {
  257. accessorUVs = new GLTFAccessor
  258. {
  259. name = "accessorUVs",
  260. bufferView = bufferViewFloatVec2.index,
  261. BufferView = bufferViewFloatVec2,
  262. componentType = GLTFAccessor.ComponentType.FLOAT,
  263. type = GLTFAccessor.TypeEnum.VEC2.ToString()
  264. };
  265. accessorUVs.index = gltf.AccessorsList.Count;
  266. gltf.AccessorsList.Add(accessorUVs);
  267. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
  268. }
  269. // Accessor - UV2
  270. GLTFAccessor accessorUV2s = null;
  271. if (hasUV2)
  272. {
  273. accessorUV2s = new GLTFAccessor
  274. {
  275. name = "accessorUV2s",
  276. bufferView = bufferViewFloatVec2.index,
  277. BufferView = bufferViewFloatVec2,
  278. componentType = GLTFAccessor.ComponentType.FLOAT,
  279. type = GLTFAccessor.TypeEnum.VEC2.ToString()
  280. };
  281. accessorUV2s.index = gltf.AccessorsList.Count;
  282. gltf.AccessorsList.Add(accessorUV2s);
  283. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
  284. }
  285. // --------------------------
  286. // - Update glTF primitive --
  287. // --------------------------
  288. RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 3);
  289. // Material
  290. if (babylonMesh.materialId != null)
  291. {
  292. // Retreive the babylon material
  293. var babylonMaterialId = babylonMesh.materialId;
  294. var babylonMaterials = new List<BabylonMaterial>(babylonScene.materials);
  295. var babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
  296. if (babylonMaterial == null)
  297. {
  298. // It's a multi material
  299. var babylonMultiMaterials = new List<BabylonMultiMaterial>(babylonScene.multiMaterials);
  300. var babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMesh.materialId);
  301. babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
  302. babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
  303. }
  304. // Update primitive material index
  305. var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
  306. if (indexMaterial == -1)
  307. {
  308. // Store material for exportation
  309. indexMaterial = babylonMaterialsToExport.Count;
  310. babylonMaterialsToExport.Add(babylonMaterial);
  311. }
  312. meshPrimitive.material = indexMaterial;
  313. // TODO - Add and retreive info from babylon material
  314. meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
  315. }
  316. // Update min and max vertex position for each component (X, Y, Z)
  317. globalVerticesSubMesh.ForEach((globalVertex) =>
  318. {
  319. var positionArray = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
  320. for (int indexComponent = 0; indexComponent < positionArray.Length; indexComponent++)
  321. {
  322. if (positionArray[indexComponent] < accessorPositions.min[indexComponent])
  323. {
  324. accessorPositions.min[indexComponent] = positionArray[indexComponent];
  325. }
  326. if (positionArray[indexComponent] > accessorPositions.max[indexComponent])
  327. {
  328. accessorPositions.max[indexComponent] = positionArray[indexComponent];
  329. }
  330. }
  331. });
  332. // Update byte length and count of accessors, bufferViews and buffers
  333. // Scalar
  334. AddElementsToAccessor(accessorIndices, _indices.Count);
  335. // Ensure the byteoffset is a multiple of 4
  336. // Indices accessor element size if 2
  337. // So the count needs to be even
  338. if (gltfIndices.Count % 2 != 0)
  339. {
  340. gltfIndices.Add(0);
  341. bufferViewScalar.byteLength += 2;
  342. buffer.byteLength += 2;
  343. }
  344. // Vector3
  345. AddElementsToAccessor(accessorPositions, globalVerticesSubMesh.Count);
  346. AddElementsToAccessor(accessorNormals, globalVerticesSubMesh.Count);
  347. // Vector4
  348. if (hasColor)
  349. {
  350. AddElementsToAccessor(accessorColors, globalVerticesSubMesh.Count);
  351. }
  352. // Vector2
  353. if (hasUV)
  354. {
  355. AddElementsToAccessor(accessorUVs, globalVerticesSubMesh.Count);
  356. }
  357. if (hasUV2)
  358. {
  359. AddElementsToAccessor(accessorUV2s, globalVerticesSubMesh.Count);
  360. }
  361. }
  362. gltfMesh.primitives = meshPrimitives.ToArray();
  363. // Update byte offset of bufferViews
  364. GLTFBufferView lastBufferView = null;
  365. gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
  366. {
  367. if (lastBufferView != null)
  368. {
  369. bufferView.byteOffset = lastBufferView.byteOffset + lastBufferView.byteLength;
  370. }
  371. lastBufferView = bufferView;
  372. });
  373. // --------------------------
  374. // --------- Saving ---------
  375. // --------------------------
  376. RaiseMessage("GLTFExporter.Mesh | saving", 2);
  377. // BufferView - Scalar
  378. gltfIndices.ForEach(n => bufferViewScalar.bytesList.AddRange(BitConverter.GetBytes(n)));
  379. // BufferView - Vector3
  380. globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
  381. {
  382. List<float> vertices = globalVerticesSubMesh.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
  383. vertices.ForEach(n => bufferViewFloatVec3.bytesList.AddRange(BitConverter.GetBytes(n)));
  384. List<float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
  385. normals.ForEach(n => bufferViewFloatVec3.bytesList.AddRange(BitConverter.GetBytes(n)));
  386. });
  387. // BufferView - Vector4
  388. globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
  389. {
  390. if (hasColor)
  391. {
  392. List<float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
  393. colors.ForEach(n => bufferViewFloatVec4.bytesList.AddRange(BitConverter.GetBytes(n)));
  394. }
  395. });
  396. // BufferView - Vector2
  397. globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
  398. {
  399. if (hasUV)
  400. {
  401. List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
  402. uvs.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
  403. }
  404. if (hasUV2)
  405. {
  406. List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
  407. uvs2.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
  408. }
  409. });
  410. //// Write data to binary file
  411. //string outputBinaryFile = Path.Combine(gltf.OutputPath, gltfMesh.name + ".bin");
  412. //RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 2);
  413. //using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
  414. //{
  415. // bytesList.ForEach(b => writer.Write(b));
  416. //}
  417. return gltfMesh;
  418. }
  419. private IPoint2 createIPoint2(float[] array, int index)
  420. {
  421. var startIndex = index * 2;
  422. return Loader.Global.Point2.Create(array[startIndex], array[startIndex + 1]);
  423. }
  424. private IPoint3 createIPoint3(float[] array, int index)
  425. {
  426. var startIndex = index * 3;
  427. return Loader.Global.Point3.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2]);
  428. }
  429. private IPoint4 createIPoint4(float[] array, int index)
  430. {
  431. var startIndex = index * 4;
  432. return Loader.Global.Point4.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2], array[startIndex + 3]);
  433. }
  434. private void AddElementsToAccessor(GLTFAccessor accessor, int count)
  435. {
  436. GLTFBufferView bufferView = accessor.BufferView;
  437. GLTFBuffer buffer = bufferView.Buffer;
  438. accessor.byteOffset = bufferView.byteLength;
  439. accessor.count += count;
  440. bufferView.byteLength += accessor.getByteLength();
  441. buffer.byteLength += accessor.getByteLength();
  442. }
  443. }
  444. }