BabylonExporter.GLTFExporter.Animation.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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 = 30.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. // --- Input ---
  33. var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
  34. // --- Output ---
  35. GLTFAccessor accessorOutput = _createAccessorOfPath(gltfTarget.path, gltf);
  36. // Populate accessor
  37. foreach (var babylonAnimationKey in babylonAnimation.keys)
  38. {
  39. var outputValues = babylonAnimationKey.values;
  40. // Store values as bytes
  41. foreach (var outputValue in outputValues)
  42. {
  43. accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
  44. }
  45. };
  46. accessorOutput.count = babylonAnimation.keys.Length;
  47. // Animation sampler
  48. var gltfAnimationSampler = new GLTFAnimationSampler
  49. {
  50. input = accessorInput.index,
  51. output = accessorOutput.index
  52. };
  53. gltfAnimationSampler.index = samplerList.Count;
  54. samplerList.Add(gltfAnimationSampler);
  55. // Channel
  56. var gltfChannel = new GLTFChannel
  57. {
  58. sampler = gltfAnimationSampler.index,
  59. target = gltfTarget
  60. };
  61. channelList.Add(gltfChannel);
  62. }
  63. }
  64. if (babylonNode.GetType() == typeof(BabylonMesh))
  65. {
  66. var babylonMesh = babylonNode as BabylonMesh;
  67. // Morph targets
  68. var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
  69. if (babylonMorphTargetManager != null)
  70. {
  71. ExportMorphTargetWeightAnimation(babylonMorphTargetManager, gltf, gltfNode, channelList, samplerList);
  72. }
  73. }
  74. // Do not export empty arrays
  75. if (channelList.Count > 0)
  76. {
  77. // Animation
  78. var gltfAnimation = new GLTFAnimation
  79. {
  80. channels = channelList.ToArray(),
  81. samplers = samplerList.ToArray()
  82. };
  83. gltf.AnimationsList.Add(gltfAnimation);
  84. return gltfAnimation;
  85. }
  86. else
  87. {
  88. return null;
  89. }
  90. }
  91. private GLTFAnimation ExportBoneAnimation(BabylonBone babylonBone, GLTF gltf, GLTFNode gltfNode)
  92. {
  93. var channelList = new List<GLTFChannel>();
  94. var samplerList = new List<GLTFAnimationSampler>();
  95. if (babylonBone.animation != null && babylonBone.animation.property == "_matrix")
  96. {
  97. RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2);
  98. var babylonAnimation = babylonBone.animation;
  99. // --- Input ---
  100. var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
  101. // --- Output ---
  102. var paths = new string[] { "translation", "rotation", "scale" };
  103. var accessorOutputByPath = new Dictionary<string, GLTFAccessor>();
  104. foreach (string path in paths)
  105. {
  106. GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
  107. accessorOutputByPath.Add(path, accessorOutput);
  108. }
  109. // Populate accessors
  110. foreach (var babylonAnimationKey in babylonAnimation.keys)
  111. {
  112. var matrix = new BabylonMatrix();
  113. matrix.m = babylonAnimationKey.values;
  114. var translationBabylon = new BabylonVector3();
  115. var rotationQuatBabylon = new BabylonQuaternion();
  116. var scaleBabylon = new BabylonVector3();
  117. matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);
  118. var outputValuesByPath = new Dictionary<string, float[]>();
  119. outputValuesByPath.Add("translation", translationBabylon.ToArray());
  120. outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
  121. outputValuesByPath.Add("scale", scaleBabylon.ToArray());
  122. // Store values as bytes
  123. foreach (string path in paths)
  124. {
  125. var accessorOutput = accessorOutputByPath[path];
  126. var outputValues = outputValuesByPath[path];
  127. foreach (var outputValue in outputValues)
  128. {
  129. accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
  130. }
  131. accessorOutput.count++;
  132. }
  133. };
  134. foreach (string path in paths)
  135. {
  136. var accessorOutput = accessorOutputByPath[path];
  137. // Animation sampler
  138. var gltfAnimationSampler = new GLTFAnimationSampler
  139. {
  140. input = accessorInput.index,
  141. output = accessorOutput.index
  142. };
  143. gltfAnimationSampler.index = samplerList.Count;
  144. samplerList.Add(gltfAnimationSampler);
  145. // Target
  146. var gltfTarget = new GLTFChannelTarget
  147. {
  148. node = gltfNode.index
  149. };
  150. gltfTarget.path = path;
  151. // Channel
  152. var gltfChannel = new GLTFChannel
  153. {
  154. sampler = gltfAnimationSampler.index,
  155. target = gltfTarget
  156. };
  157. channelList.Add(gltfChannel);
  158. }
  159. }
  160. // Do not export empty arrays
  161. if (channelList.Count > 0)
  162. {
  163. // Animation
  164. var gltfAnimation = new GLTFAnimation
  165. {
  166. channels = channelList.ToArray(),
  167. samplers = samplerList.ToArray()
  168. };
  169. gltf.AnimationsList.Add(gltfAnimation);
  170. return gltfAnimation;
  171. }
  172. else
  173. {
  174. return null;
  175. }
  176. }
  177. private GLTFAccessor _createAndPopulateInput(GLTF gltf, BabylonAnimation babylonAnimation)
  178. {
  179. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  180. var accessorInput = GLTFBufferService.Instance.CreateAccessor(
  181. gltf,
  182. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  183. "accessorAnimationInput",
  184. GLTFAccessor.ComponentType.FLOAT,
  185. GLTFAccessor.TypeEnum.SCALAR
  186. );
  187. // Populate accessor
  188. accessorInput.min = new float[] { float.MaxValue };
  189. accessorInput.max = new float[] { float.MinValue };
  190. foreach (var babylonAnimationKey in babylonAnimation.keys)
  191. {
  192. var inputValue = babylonAnimationKey.frame / FPS_FACTOR;
  193. // Store values as bytes
  194. accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
  195. // Update min and max values
  196. GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
  197. };
  198. accessorInput.count = babylonAnimation.keys.Length;
  199. return accessorInput;
  200. }
  201. private GLTFAccessor _createAccessorOfPath(string path, GLTF gltf)
  202. {
  203. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  204. GLTFAccessor accessorOutput = null;
  205. switch (path)
  206. {
  207. case "translation":
  208. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  209. gltf,
  210. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
  211. "accessorAnimationPositions",
  212. GLTFAccessor.ComponentType.FLOAT,
  213. GLTFAccessor.TypeEnum.VEC3
  214. );
  215. break;
  216. case "rotation":
  217. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  218. gltf,
  219. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
  220. "accessorAnimationRotations",
  221. GLTFAccessor.ComponentType.FLOAT,
  222. GLTFAccessor.TypeEnum.VEC4
  223. );
  224. break;
  225. case "scale":
  226. accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  227. gltf,
  228. GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
  229. "accessorAnimationScales",
  230. GLTFAccessor.ComponentType.FLOAT,
  231. GLTFAccessor.TypeEnum.VEC3
  232. );
  233. break;
  234. }
  235. return accessorOutput;
  236. }
  237. private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List<GLTFChannel> channelList, List<GLTFAnimationSampler> samplerList)
  238. {
  239. if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
  240. {
  241. return false;
  242. }
  243. RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);
  244. var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
  245. var frames = new List<int>(influencesPerFrame.Keys);
  246. frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand
  247. // Target
  248. var gltfTarget = new GLTFChannelTarget
  249. {
  250. node = gltfNode.index
  251. };
  252. gltfTarget.path = "weights";
  253. // Buffer
  254. var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
  255. // --- Input ---
  256. var accessorInput = GLTFBufferService.Instance.CreateAccessor(
  257. gltf,
  258. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  259. "accessorAnimationInput",
  260. GLTFAccessor.ComponentType.FLOAT,
  261. GLTFAccessor.TypeEnum.SCALAR
  262. );
  263. // Populate accessor
  264. accessorInput.min = new float[] { float.MaxValue };
  265. accessorInput.max = new float[] { float.MinValue };
  266. foreach (var frame in frames)
  267. {
  268. var inputValue = frame / FPS_FACTOR;
  269. // Store values as bytes
  270. accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
  271. // Update min and max values
  272. GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
  273. }
  274. accessorInput.count = influencesPerFrame.Count;
  275. // --- Output ---
  276. GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor(
  277. gltf,
  278. GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
  279. "accessorAnimationWeights",
  280. GLTFAccessor.ComponentType.FLOAT,
  281. GLTFAccessor.TypeEnum.SCALAR
  282. );
  283. // Populate accessor
  284. foreach (var frame in frames)
  285. {
  286. var outputValues = influencesPerFrame[frame];
  287. // Store values as bytes
  288. foreach (var outputValue in outputValues)
  289. {
  290. accessorOutput.count++;
  291. accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
  292. }
  293. }
  294. // Animation sampler
  295. var gltfAnimationSampler = new GLTFAnimationSampler
  296. {
  297. input = accessorInput.index,
  298. output = accessorOutput.index
  299. };
  300. gltfAnimationSampler.index = samplerList.Count;
  301. samplerList.Add(gltfAnimationSampler);
  302. // Channel
  303. var gltfChannel = new GLTFChannel
  304. {
  305. sampler = gltfAnimationSampler.index,
  306. target = gltfTarget
  307. };
  308. channelList.Add(gltfChannel);
  309. return true;
  310. }
  311. private bool _isBabylonMorphTargetManagerAnimationValid(BabylonMorphTargetManager babylonMorphTargetManager)
  312. {
  313. bool hasAnimation = false;
  314. bool areAnimationsValid = true;
  315. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  316. {
  317. if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
  318. {
  319. hasAnimation = true;
  320. // Ensure target has only one animation
  321. if (babylonMorphTarget.animations.Length > 1)
  322. {
  323. areAnimationsValid = false;
  324. RaiseWarning("GLTFExporter.Animation | Only one animation is supported for morph targets", 3);
  325. continue;
  326. }
  327. // Ensure the target animation property is 'influence'
  328. bool targetHasInfluence = false;
  329. foreach (BabylonAnimation babylonAnimation in babylonMorphTarget.animations)
  330. {
  331. if (babylonAnimation.property == "influence")
  332. {
  333. targetHasInfluence = true;
  334. }
  335. }
  336. if (targetHasInfluence == false)
  337. {
  338. areAnimationsValid = false;
  339. RaiseWarning("GLTFExporter.Animation | Only 'influence' animation is supported for morph targets", 3);
  340. continue;
  341. }
  342. }
  343. }
  344. return hasAnimation && areAnimationsValid;
  345. }
  346. /// <summary>
  347. /// The keys of each BabylonMorphTarget animation ARE NOT assumed to be identical.
  348. /// This function merges together all keys and binds to each an influence value for all targets.
  349. /// A target influence value is automatically computed when necessary.
  350. /// Computation rules are:
  351. /// - linear interpolation between target key range
  352. /// - constant value outside target key range
  353. /// </summary>
  354. /// <example>
  355. /// When:
  356. /// animation1.keys = {0, 25, 50, 100}
  357. /// animation2.keys = {50, 75, 100}
  358. ///
  359. /// Gives:
  360. /// mergedKeys = {0, 25, 50, 100, 75}
  361. /// range1=[0, 100]
  362. /// range2=[50, 100]
  363. /// for animation1, the value associated to key=75 is the interpolation of its values between 50 and 100
  364. /// 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>
  365. /// <param name="babylonMorphTargetManager"></param>
  366. /// <returns>A map which for each frame, gives the influence value of all targets</returns>
  367. private Dictionary<int, List<float>> _getTargetManagerAnimationsData(BabylonMorphTargetManager babylonMorphTargetManager)
  368. {
  369. // Merge all keys into a single set (no duplicated frame)
  370. var mergedFrames = new HashSet<int>();
  371. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  372. {
  373. if (babylonMorphTarget.animations != null)
  374. {
  375. var animation = babylonMorphTarget.animations[0];
  376. foreach (BabylonAnimationKey animationKey in animation.keys)
  377. {
  378. mergedFrames.Add(animationKey.frame);
  379. }
  380. }
  381. }
  382. // For each frame, gives the influence value of all targets (gltf structure)
  383. var influencesPerFrame = new Dictionary<int, List<float>>();
  384. foreach (var frame in mergedFrames)
  385. {
  386. influencesPerFrame.Add(frame, new List<float>());
  387. }
  388. foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
  389. {
  390. // For a given target, for each frame, gives the influence value of the target (babylon structure)
  391. var influencePerFrameForTarget = new Dictionary<int, float>();
  392. if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
  393. {
  394. var animation = babylonMorphTarget.animations[0];
  395. if (animation.keys.Length == 1)
  396. {
  397. // Same influence for all frames
  398. var influence = animation.keys[0].values[0];
  399. foreach (var frame in mergedFrames)
  400. {
  401. influencePerFrameForTarget.Add(frame, influence);
  402. }
  403. }
  404. else
  405. {
  406. // Retreive target animation key range [min, max]
  407. var babylonAnimationKeys = new List<BabylonAnimationKey>(animation.keys);
  408. babylonAnimationKeys.Sort();
  409. var minAnimationKey = babylonAnimationKeys[0];
  410. var maxAnimationKey = babylonAnimationKeys[babylonAnimationKeys.Count - 1];
  411. foreach (var frame in mergedFrames)
  412. {
  413. // Surround the current frame with closest keys available for the target
  414. BabylonAnimationKey lowerAnimationKey = minAnimationKey;
  415. BabylonAnimationKey upperAnimationKey = maxAnimationKey;
  416. foreach (BabylonAnimationKey animationKey in animation.keys)
  417. {
  418. if (lowerAnimationKey.frame < animationKey.frame && animationKey.frame <= frame)
  419. {
  420. lowerAnimationKey = animationKey;
  421. }
  422. if (frame <= animationKey.frame && animationKey.frame < upperAnimationKey.frame)
  423. {
  424. upperAnimationKey = animationKey;
  425. }
  426. }
  427. // In case the target has a key for this frame
  428. // or the current frame is out of target animation key range
  429. if (lowerAnimationKey.frame == upperAnimationKey.frame)
  430. {
  431. influencePerFrameForTarget.Add(frame, lowerAnimationKey.values[0]);
  432. }
  433. else
  434. {
  435. // Interpolate influence values
  436. var t = 1.0f * (frame - lowerAnimationKey.frame) / (upperAnimationKey.frame - lowerAnimationKey.frame);
  437. var influence = Tools.Lerp(lowerAnimationKey.values[0], upperAnimationKey.values[0], t);
  438. influencePerFrameForTarget.Add(frame, influence);
  439. }
  440. }
  441. }
  442. }
  443. else
  444. {
  445. // Target is not animated
  446. // Fill all frames with 0
  447. foreach (var frame in mergedFrames)
  448. {
  449. influencePerFrameForTarget.Add(frame, 0);
  450. }
  451. }
  452. // Switch from babylon to gltf storage representation
  453. foreach (var frame in mergedFrames)
  454. {
  455. List<float> influences = influencesPerFrame[frame];
  456. influences.Add(influencePerFrameForTarget[frame]);
  457. }
  458. }
  459. return influencesPerFrame;
  460. }
  461. private string _getTargetPath(string babylonProperty)
  462. {
  463. switch (babylonProperty)
  464. {
  465. case "position":
  466. return "translation";
  467. case "rotationQuaternion":
  468. return "rotation";
  469. case "scaling":
  470. return "scale";
  471. default:
  472. return null;
  473. }
  474. }
  475. }
  476. }