BabylonExporter.GLTFExporter.Mesh.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. using Autodesk.Max;
  2. using BabylonExport.Entities;
  3. using GLTFExport.Entities;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. namespace Max2Babylon
  8. {
  9. partial class BabylonExporter
  10. {
  11. private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
  12. {
  13. RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);
  14. // --------------------------
  15. // --- Mesh from babylon ----
  16. // --------------------------
  17. if (babylonMesh.positions == null)
  18. {
  19. RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
  20. return null;
  21. }
  22. RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
  23. // Retreive general data from babylon mesh
  24. int nbVertices = babylonMesh.positions.Length / 3;
  25. bool hasUV = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0;
  26. bool hasUV2 = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0;
  27. bool hasColor = babylonMesh.colors != null && babylonMesh.colors.Length > 0;
  28. bool hasBones = babylonMesh.matricesIndices != null && babylonMesh.matricesIndices.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. RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3);
  34. // Retreive vertices data from babylon mesh
  35. List<GLTFGlobalVertex> globalVertices = new List<GLTFGlobalVertex>();
  36. for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
  37. {
  38. GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
  39. globalVertex.Position = Tools.CreateIPoint3FromArray(babylonMesh.positions, indexVertex);
  40. globalVertex.Normal = Tools.CreateIPoint3FromArray(babylonMesh.normals, indexVertex);
  41. if (hasUV)
  42. {
  43. globalVertex.UV = Tools.CreateIPoint2FromArray(babylonMesh.uvs, indexVertex);
  44. // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
  45. // While for Babylon, it corresponds to the lower left corner of a texture image
  46. globalVertex.UV.Y = 1 - globalVertex.UV.Y;
  47. }
  48. if (hasUV2)
  49. {
  50. globalVertex.UV2 = Tools.CreateIPoint2FromArray(babylonMesh.uvs2, indexVertex);
  51. // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
  52. // While for Babylon, it corresponds to the lower left corner of a texture image
  53. globalVertex.UV2.Y = 1 - globalVertex.UV2.Y;
  54. }
  55. if (hasColor)
  56. {
  57. globalVertex.Color = Tools.CreateIPoint4FromArray(babylonMesh.colors, indexVertex).ToArray();
  58. }
  59. if (hasBones)
  60. {
  61. // In babylon, the 4 bones indices are stored in a single int
  62. // Each bone index is 8-bit offset from the next
  63. int bonesIndicesMerged = babylonMesh.matricesIndices[indexVertex];
  64. int bone3 = bonesIndicesMerged >> 24;
  65. bonesIndicesMerged -= bone3 << 24;
  66. int bone2 = bonesIndicesMerged >> 16;
  67. bonesIndicesMerged -= bone2 << 16;
  68. int bone1 = bonesIndicesMerged >> 8;
  69. bonesIndicesMerged -= bone1 << 8;
  70. int bone0 = bonesIndicesMerged >> 0;
  71. bonesIndicesMerged -= bone0 << 0;
  72. var bonesIndicesArray = new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 };
  73. globalVertex.BonesIndices = bonesIndicesArray;
  74. globalVertex.BonesWeights = Tools.CreateIPoint4FromArray(babylonMesh.matricesWeights, indexVertex).ToArray();
  75. }
  76. globalVertices.Add(globalVertex);
  77. }
  78. // Retreive indices from babylon mesh
  79. List<ushort> babylonIndices = new List<ushort>();
  80. babylonIndices = babylonMesh.indices.ToList().ConvertAll(new Converter<int, ushort>(n => (ushort)n));
  81. // --------------------------
  82. // ------- Init glTF --------
  83. // --------------------------
  84. RaiseMessage("GLTFExporter.Mesh | Init glTF", 2);
  85. // Mesh
  86. var gltfMesh = new GLTFMesh { name = babylonMesh.name };
  87. gltfMesh.index = gltf.MeshesList.Count;
  88. gltf.MeshesList.Add(gltfMesh);
  89. gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
  90. if (hasBones)
  91. {
  92. gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId;
  93. }
  94. // --------------------------
  95. // ---- glTF primitives -----
  96. // --------------------------
  97. RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
  98. var meshPrimitives = new List<GLTFMeshPrimitive>();
  99. var weights = new List<float>();
  100. foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
  101. {
  102. // --------------------------
  103. // ------ SubMesh data ------
  104. // --------------------------
  105. List<GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);
  106. List<ushort> gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
  107. // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
  108. // Thus, the gltf indices list is a concatenation of sub lists all 0-based
  109. // Example for 2 triangles, each being a submesh:
  110. // babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
  111. var minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
  112. for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
  113. {
  114. gltfIndices[indexIndice] -= minIndiceValue;
  115. }
  116. // --------------------------
  117. // ----- Mesh primitive -----
  118. // --------------------------
  119. // MeshPrimitive
  120. var meshPrimitive = new GLTFMeshPrimitive
  121. {
  122. attributes = new Dictionary<string, int>()
  123. };
  124. meshPrimitives.Add(meshPrimitive);
  125. // Material
  126. if (babylonMesh.materialId != null)
  127. {
  128. // Retreive the babylon material
  129. var babylonMaterialId = babylonMesh.materialId;
  130. var babylonMaterials = new List<BabylonMaterial>(babylonScene.materials);
  131. var babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
  132. if (babylonMaterial == null)
  133. {
  134. // It's a multi material
  135. var babylonMultiMaterials = new List<BabylonMultiMaterial>(babylonScene.multiMaterials);
  136. var babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMesh.materialId);
  137. babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
  138. babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
  139. }
  140. // Update primitive material index
  141. var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
  142. if (indexMaterial == -1)
  143. {
  144. // Store material for exportation
  145. indexMaterial = babylonMaterialsToExport.Count;
  146. babylonMaterialsToExport.Add(babylonMaterial);
  147. }
  148. meshPrimitive.material = indexMaterial;
  149. // TODO - Add and retreive info from babylon material
  150. meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
  151. }
  152. // --------------------------
  153. // ------- Accessors --------
  154. // --------------------------
  155. // Buffer
  156. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  157. // --- Indices ---
  158. var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
  159. gltf,
  160. GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
  161. "accessorIndices",
  162. GLTFAccessor.ComponentType.UNSIGNED_SHORT,
  163. GLTFAccessor.TypeEnum.SCALAR
  164. );
  165. meshPrimitive.indices = accessorIndices.index;
  166. // Populate accessor
  167. gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
  168. accessorIndices.count = gltfIndices.Count;
  169. // --- Positions ---
  170. var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
  171. gltf,
  172. GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
  173. "accessorPositions",
  174. GLTFAccessor.ComponentType.FLOAT,
  175. GLTFAccessor.TypeEnum.VEC3
  176. );
  177. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
  178. // Populate accessor
  179. accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
  180. accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
  181. globalVerticesSubMesh.ForEach((globalVertex) =>
  182. {
  183. var positions = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
  184. // Store values as bytes
  185. foreach (var position in positions)
  186. {
  187. accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
  188. }
  189. // Update min and max values
  190. GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
  191. });
  192. accessorPositions.count = globalVerticesSubMesh.Count;
  193. // --- Normals ---
  194. var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
  195. gltf,
  196. GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
  197. "accessorNormals",
  198. GLTFAccessor.ComponentType.FLOAT,
  199. GLTFAccessor.TypeEnum.VEC3
  200. );
  201. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
  202. // Populate accessor
  203. List<float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
  204. normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
  205. accessorNormals.count = globalVerticesSubMesh.Count;
  206. // --- Colors ---
  207. if (hasColor)
  208. {
  209. var accessorColors = GLTFBufferService.Instance.CreateAccessor(
  210. gltf,
  211. GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
  212. "accessorColors",
  213. GLTFAccessor.ComponentType.FLOAT,
  214. GLTFAccessor.TypeEnum.VEC4
  215. );
  216. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
  217. // Populate accessor
  218. List<float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
  219. colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
  220. accessorColors.count = globalVerticesSubMesh.Count;
  221. }
  222. // --- UV ---
  223. if (hasUV)
  224. {
  225. var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
  226. gltf,
  227. GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
  228. "accessorUVs",
  229. GLTFAccessor.ComponentType.FLOAT,
  230. GLTFAccessor.TypeEnum.VEC2
  231. );
  232. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
  233. // Populate accessor
  234. List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
  235. uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
  236. accessorUVs.count = globalVerticesSubMesh.Count;
  237. }
  238. // --- UV2 ---
  239. if (hasUV2)
  240. {
  241. var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
  242. gltf,
  243. GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
  244. "accessorUV2s",
  245. GLTFAccessor.ComponentType.FLOAT,
  246. GLTFAccessor.TypeEnum.VEC2
  247. );
  248. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
  249. // Populate accessor
  250. List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
  251. uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
  252. accessorUV2s.count = globalVerticesSubMesh.Count;
  253. }
  254. // --- Bones ---
  255. if (hasBones)
  256. {
  257. // --- Joints ---
  258. var accessorJoints = GLTFBufferService.Instance.CreateAccessor(
  259. gltf,
  260. GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
  261. "accessorJoints",
  262. GLTFAccessor.ComponentType.UNSIGNED_SHORT,
  263. GLTFAccessor.TypeEnum.VEC4
  264. );
  265. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index);
  266. // Populate accessor
  267. List<ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList();
  268. joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n)));
  269. accessorJoints.count = globalVerticesSubMesh.Count;
  270. // --- Weights ---
  271. var accessorWeights = GLTFBufferService.Instance.CreateAccessor(
  272. gltf,
  273. GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
  274. "accessorWeights",
  275. GLTFAccessor.ComponentType.FLOAT,
  276. GLTFAccessor.TypeEnum.VEC4
  277. );
  278. meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index);
  279. // Populate accessor
  280. List<float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList();
  281. weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n)));
  282. accessorWeights.count = globalVerticesSubMesh.Count;
  283. }
  284. // Morph targets
  285. var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
  286. if (babylonMorphTargetManager != null)
  287. {
  288. _exportMorphTargets(babylonMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive, weights);
  289. }
  290. }
  291. gltfMesh.primitives = meshPrimitives.ToArray();
  292. if (weights.Count > 0)
  293. {
  294. gltfMesh.weights = weights.ToArray();
  295. }
  296. return gltfMesh;
  297. }
  298. private BabylonMorphTargetManager GetBabylonMorphTargetManager(BabylonScene babylonScene, BabylonMesh babylonMesh)
  299. {
  300. if (babylonMesh.morphTargetManagerId.HasValue)
  301. {
  302. if (babylonScene.morphTargetManagers == null)
  303. {
  304. RaiseWarning("GLTFExporter.Mesh | morphTargetManagers is not defined", 3);
  305. }
  306. else
  307. {
  308. var babylonMorphTargetManager = babylonScene.morphTargetManagers.ElementAtOrDefault(babylonMesh.morphTargetManagerId.Value);
  309. if (babylonMorphTargetManager == null)
  310. {
  311. RaiseWarning($"GLTFExporter.Mesh | morphTargetManager with index {babylonMesh.morphTargetManagerId.Value} not found", 3);
  312. }
  313. return babylonMorphTargetManager;
  314. }
  315. }
  316. return null;
  317. }
  318. private void _exportMorphTargets(BabylonMesh babylonMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive, List<float> weights)
  319. {
  320. var gltfMorphTargets = new List<GLTFMorphTarget>();
  321. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  322. {
  323. var gltfMorphTarget = new GLTFMorphTarget();
  324. // Positions
  325. if (babylonMorphTarget.positions != null)
  326. {
  327. var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor(
  328. gltf,
  329. GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
  330. "accessorTargetPositions",
  331. GLTFAccessor.ComponentType.FLOAT,
  332. GLTFAccessor.TypeEnum.VEC3
  333. );
  334. gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index);
  335. // Populate accessor
  336. accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
  337. accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
  338. for (int indexPosition = 0; indexPosition < babylonMorphTarget.positions.Length; indexPosition += 3)
  339. {
  340. var positionTarget = Tools.SubArray(babylonMorphTarget.positions, indexPosition, 3);
  341. // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
  342. var positionMesh = Tools.SubArray(babylonMesh.positions, indexPosition, 3);
  343. for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++)
  344. {
  345. positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate];
  346. }
  347. // Store values as bytes
  348. foreach (var coordinate in positionTarget)
  349. {
  350. accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate));
  351. }
  352. // Update min and max values
  353. GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget);
  354. }
  355. accessorTargetPositions.count = babylonMorphTarget.positions.Length / 3;
  356. }
  357. // Normals
  358. if (babylonMorphTarget.normals != null)
  359. {
  360. var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor(
  361. gltf,
  362. GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
  363. "accessorTargetNormals",
  364. GLTFAccessor.ComponentType.FLOAT,
  365. GLTFAccessor.TypeEnum.VEC3
  366. );
  367. gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index);
  368. // Populate accessor
  369. for (int indexNormal = 0; indexNormal < babylonMorphTarget.positions.Length; indexNormal += 3)
  370. {
  371. var normalTarget = Tools.SubArray(babylonMorphTarget.normals, indexNormal, 3);
  372. // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
  373. var normalMesh = Tools.SubArray(babylonMesh.normals, indexNormal, 3);
  374. for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++)
  375. {
  376. normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate];
  377. }
  378. // Store values as bytes
  379. foreach (var coordinate in normalTarget)
  380. {
  381. accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate));
  382. }
  383. }
  384. accessorTargetNormals.count = babylonMorphTarget.normals.Length / 3;
  385. }
  386. if (gltfMorphTarget.Count > 0)
  387. {
  388. gltfMorphTargets.Add(gltfMorphTarget);
  389. weights.Add(babylonMorphTarget.influence);
  390. }
  391. }
  392. if (gltfMorphTargets.Count > 0)
  393. {
  394. meshPrimitive.targets = gltfMorphTargets.ToArray();
  395. }
  396. }
  397. }
  398. }