BabylonExporter.GLTFExporter.Animation.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. using BabylonExport.Entities;
  2. using GLTFExport.Entities;
  3. using System;
  4. using System.Collections.Generic;
  5. namespace Max2Babylon
  6. {
  7. partial class BabylonExporter
  8. {
  9. private static float FPS_FACTOR = 60.0f; // TODO - Which FPS factor ?
  10. private GLTFAnimation ExportNodeAnimation(BabylonNode babylonNode, GLTF gltf, GLTFNode gltfNode, BabylonScene babylonScene = null)
  11. {
  12. var channelList = new List<GLTFChannel>();
  13. var samplerList = new List<GLTFAnimationSampler>();
  14. if (babylonNode.animations != null && babylonNode.animations.Length > 0)
  15. {
  16. RaiseMessage("GLTFExporter.Animation | Export animation of node named: " + babylonNode.name, 2);
  17. foreach (BabylonAnimation babylonAnimation in babylonNode.animations)
  18. {
  19. // Target
  20. var gltfTarget = new GLTFChannelTarget
  21. {
  22. node = gltfNode.index
  23. };
  24. gltfTarget.path = _getTargetPath(babylonAnimation.property);
  25. if (gltfTarget.path == null)
  26. {
  27. // Unkown babylon animation property
  28. RaiseWarning("GLTFExporter.Animation | Unkown animation property '" + babylonAnimation.property + "'", 3);
  29. // Ignore this babylon animation
  30. continue;
  31. }
  32. // Buffer
  33. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  34. // --- Input ---
  35. var accessorInput = GLTFBufferService.Instance.CreateAccessor(
  36. gltf,
  37. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  38. "accessorAnimationInput",
  39. GLTFAccessor.ComponentType.FLOAT,
  40. GLTFAccessor.TypeEnum.SCALAR
  41. );
  42. // Populate accessor
  43. accessorInput.min = new float[] { float.MaxValue };
  44. accessorInput.max = new float[] { float.MinValue };
  45. foreach (var babylonAnimationKey in babylonAnimation.keys)
  46. {
  47. var inputValue = babylonAnimationKey.frame / FPS_FACTOR;
  48. // Store values as bytes
  49. accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
  50. // Update min and max values
  51. GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
  52. };
  53. accessorInput.count = babylonAnimation.keys.Length;
  54. // --- Output ---
  55. GLTFAccessor accessorOutput = null;
  56. switch (gltfTarget.path)
  57. {
  58. case "translation":
  59. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  60. gltf,
  61. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
  62. "accessorAnimationPositions",
  63. GLTFAccessor.ComponentType.FLOAT,
  64. GLTFAccessor.TypeEnum.VEC3
  65. );
  66. break;
  67. case "rotation":
  68. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  69. gltf,
  70. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
  71. "accessorAnimationRotations",
  72. GLTFAccessor.ComponentType.FLOAT,
  73. GLTFAccessor.TypeEnum.VEC4
  74. );
  75. break;
  76. case "scale":
  77. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  78. gltf,
  79. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
  80. "accessorAnimationScales",
  81. GLTFAccessor.ComponentType.FLOAT,
  82. GLTFAccessor.TypeEnum.VEC3
  83. );
  84. break;
  85. }
  86. // Populate accessor
  87. foreach (var babylonAnimationKey in babylonAnimation.keys)
  88. {
  89. var outputValues = babylonAnimationKey.values;
  90. // Store values as bytes
  91. foreach (var outputValue in outputValues)
  92. {
  93. accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
  94. }
  95. };
  96. accessorOutput.count = babylonAnimation.keys.Length;
  97. // Animation sampler
  98. var gltfAnimationSampler = new GLTFAnimationSampler
  99. {
  100. input = accessorInput.index,
  101. output = accessorOutput.index
  102. };
  103. gltfAnimationSampler.index = samplerList.Count;
  104. samplerList.Add(gltfAnimationSampler);
  105. // Channel
  106. var gltfChannel = new GLTFChannel
  107. {
  108. sampler = gltfAnimationSampler.index,
  109. target = gltfTarget
  110. };
  111. channelList.Add(gltfChannel);
  112. }
  113. }
  114. if (babylonNode.GetType() == typeof(BabylonMesh))
  115. {
  116. var babylonMesh = babylonNode as BabylonMesh;
  117. // Morph targets
  118. var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
  119. if (babylonMorphTargetManager != null)
  120. {
  121. ExportMorphTargetWeightAnimation(babylonMorphTargetManager, gltf, gltfNode, channelList, samplerList);
  122. }
  123. }
  124. // Do not export empty arrays
  125. if (channelList.Count > 0)
  126. {
  127. // Animation
  128. var gltfAnimation = new GLTFAnimation
  129. {
  130. channels = channelList.ToArray(),
  131. samplers = samplerList.ToArray()
  132. };
  133. gltf.AnimationsList.Add(gltfAnimation);
  134. return gltfAnimation;
  135. }
  136. else
  137. {
  138. return null;
  139. }
  140. }
  141. private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List<GLTFChannel> channelList, List<GLTFAnimationSampler> samplerList)
  142. {
  143. if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
  144. {
  145. return false;
  146. }
  147. RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);
  148. var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
  149. var frames = new List<int>(influencesPerFrame.Keys);
  150. frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand
  151. // Target
  152. var gltfTarget = new GLTFChannelTarget
  153. {
  154. node = gltfNode.index
  155. };
  156. gltfTarget.path = "weights";
  157. // Buffer
  158. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  159. // --- Input ---
  160. var accessorInput = GLTFBufferService.Instance.CreateAccessor(
  161. gltf,
  162. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  163. "accessorAnimationInput",
  164. GLTFAccessor.ComponentType.FLOAT,
  165. GLTFAccessor.TypeEnum.SCALAR
  166. );
  167. // Populate accessor
  168. accessorInput.min = new float[] { float.MaxValue };
  169. accessorInput.max = new float[] { float.MinValue };
  170. foreach (var frame in frames)
  171. {
  172. var inputValue = frame / FPS_FACTOR;
  173. // Store values as bytes
  174. accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
  175. // Update min and max values
  176. GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
  177. }
  178. accessorInput.count = influencesPerFrame.Count;
  179. // --- Output ---
  180. GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  181. gltf,
  182. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  183. "accessorAnimationWeights",
  184. GLTFAccessor.ComponentType.FLOAT,
  185. GLTFAccessor.TypeEnum.SCALAR
  186. );
  187. // Populate accessor
  188. foreach (var frame in frames)
  189. {
  190. var outputValues = influencesPerFrame[frame];
  191. // Store values as bytes
  192. foreach (var outputValue in outputValues)
  193. {
  194. accessorOutput.count++;
  195. accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
  196. }
  197. }
  198. // Animation sampler
  199. var gltfAnimationSampler = new GLTFAnimationSampler
  200. {
  201. input = accessorInput.index,
  202. output = accessorOutput.index
  203. };
  204. gltfAnimationSampler.index = samplerList.Count;
  205. samplerList.Add(gltfAnimationSampler);
  206. // Channel
  207. var gltfChannel = new GLTFChannel
  208. {
  209. sampler = gltfAnimationSampler.index,
  210. target = gltfTarget
  211. };
  212. channelList.Add(gltfChannel);
  213. return true;
  214. }
  215. private bool _isBabylonMorphTargetManagerAnimationValid(BabylonMorphTargetManager babylonMorphTargetManager)
  216. {
  217. bool hasAnimation = false;
  218. bool areAnimationsValid = true;
  219. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  220. {
  221. if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
  222. {
  223. hasAnimation = true;
  224. // Ensure target has only one animation
  225. if (babylonMorphTarget.animations.Length > 1)
  226. {
  227. areAnimationsValid = false;
  228. RaiseWarning("GLTFExporter.Animation | Only one animation is supported for morph targets", 3);
  229. continue;
  230. }
  231. // Ensure the target animation property is 'influence'
  232. bool targetHasInfluence = false;
  233. foreach (BabylonAnimation babylonAnimation in babylonMorphTarget.animations)
  234. {
  235. if (babylonAnimation.property == "influence")
  236. {
  237. targetHasInfluence = true;
  238. }
  239. }
  240. if (targetHasInfluence == false)
  241. {
  242. areAnimationsValid = false;
  243. RaiseWarning("GLTFExporter.Animation | Only 'influence' animation is supported for morph targets", 3);
  244. continue;
  245. }
  246. }
  247. }
  248. return hasAnimation && areAnimationsValid;
  249. }
  250. /// <summary>
  251. /// The keys of each BabylonMorphTarget animation ARE NOT assumed to be identical.
  252. /// This function merges together all keys and binds to each an influence value for all targets.
  253. /// A target influence value is automatically computed when necessary.
  254. /// Computation rules are:
  255. /// - linear interpolation between target key range
  256. /// - constant value outside target key range
  257. /// </summary>
  258. /// <example>
  259. /// When:
  260. /// animation1.keys = {0, 25, 50, 100}
  261. /// animation2.keys = {50, 75, 100}
  262. ///
  263. /// Gives:
  264. /// mergedKeys = {0, 25, 50, 100, 75}
  265. /// range1=[0, 100]
  266. /// range2=[50, 100]
  267. /// for animation1, the value associated to key=75 is the interpolation of its values between 50 and 100
  268. /// for animation2, the value associated to key=0 is equal to the one at key=50 since 0 is out of range [50, 100] (same for key=25)</example>
  269. /// <param name="babylonMorphTargetManager"></param>
  270. /// <returns>A map which for each frame, gives the influence value of all targets</returns>
  271. private Dictionary<int, List<float>> _getTargetManagerAnimationsData(BabylonMorphTargetManager babylonMorphTargetManager)
  272. {
  273. // Merge all keys into a single set (no duplicated frame)
  274. var mergedFrames = new HashSet<int>();
  275. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  276. {
  277. if (babylonMorphTarget.animations != null)
  278. {
  279. var animation = babylonMorphTarget.animations[0];
  280. foreach (BabylonAnimationKey animationKey in animation.keys)
  281. {
  282. mergedFrames.Add(animationKey.frame);
  283. }
  284. }
  285. }
  286. // For each frame, gives the influence value of all targets (gltf structure)
  287. var influencesPerFrame = new Dictionary<int, List<float>>();
  288. foreach (var frame in mergedFrames)
  289. {
  290. influencesPerFrame.Add(frame, new List<float>());
  291. }
  292. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  293. {
  294. // For a given target, for each frame, gives the influence value of the target (babylon structure)
  295. var influencePerFrameForTarget = new Dictionary<int, float>();
  296. if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
  297. {
  298. var animation = babylonMorphTarget.animations[0];
  299. if (animation.keys.Length == 1)
  300. {
  301. // Same influence for all frames
  302. var influence = animation.keys[0].values[0];
  303. foreach (var frame in mergedFrames)
  304. {
  305. influencePerFrameForTarget.Add(frame, influence);
  306. }
  307. }
  308. else
  309. {
  310. // Retreive target animation key range [min, max]
  311. var babylonAnimationKeys = new List<BabylonAnimationKey>(animation.keys);
  312. babylonAnimationKeys.Sort();
  313. var minAnimationKey = babylonAnimationKeys[0];
  314. var maxAnimationKey = babylonAnimationKeys[babylonAnimationKeys.Count - 1];
  315. foreach (var frame in mergedFrames)
  316. {
  317. // Surround the current frame with closest keys available for the target
  318. BabylonAnimationKey lowerAnimationKey = minAnimationKey;
  319. BabylonAnimationKey upperAnimationKey = maxAnimationKey;
  320. foreach (BabylonAnimationKey animationKey in animation.keys)
  321. {
  322. if (lowerAnimationKey.frame < animationKey.frame && animationKey.frame <= frame)
  323. {
  324. lowerAnimationKey = animationKey;
  325. }
  326. if (frame <= animationKey.frame && animationKey.frame < upperAnimationKey.frame)
  327. {
  328. upperAnimationKey = animationKey;
  329. }
  330. }
  331. // In case the target has a key for this frame
  332. // or the current frame is out of target animation key range
  333. if (lowerAnimationKey.frame == upperAnimationKey.frame)
  334. {
  335. influencePerFrameForTarget.Add(frame, lowerAnimationKey.values[0]);
  336. }
  337. else
  338. {
  339. // Interpolate influence values
  340. var t = 1.0f * (frame - lowerAnimationKey.frame) / (upperAnimationKey.frame - lowerAnimationKey.frame);
  341. var influence = Tools.Lerp(lowerAnimationKey.values[0], upperAnimationKey.values[0], t);
  342. influencePerFrameForTarget.Add(frame, influence);
  343. }
  344. }
  345. }
  346. }
  347. else
  348. {
  349. // Target is not animated
  350. // Fill all frames with 0
  351. foreach (var frame in mergedFrames)
  352. {
  353. influencePerFrameForTarget.Add(frame, 0);
  354. }
  355. }
  356. // Switch from babylon to gltf storage representation
  357. foreach (var frame in mergedFrames)
  358. {
  359. List<float> influences = influencesPerFrame[frame];
  360. influences.Add(influencePerFrameForTarget[frame]);
  361. }
  362. }
  363. return influencesPerFrame;
  364. }
  365. private string _getTargetPath(string babylonProperty)
  366. {
  367. switch (babylonProperty)
  368. {
  369. case "position":
  370. return "translation";
  371. case "rotationQuaternion":
  372. return "rotation";
  373. case "scaling":
  374. return "scale";
  375. default:
  376. return null;
  377. }
  378. }
  379. }
  380. }