AnimatedModelProcessor.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // AnimatedModelProcessor.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using CustomModelAnimation;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Content.Pipeline;
  15. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  16. using Microsoft.Xna.Framework.Content.Pipeline.Processors;
  17. #endregion
  18. namespace CustomModelAnimationPipeline
  19. {
  20. /// <summary>
  21. /// Custom processor extends the builtin framework ModelProcessor class,
  22. /// adding animation support.
  23. /// </summary>
  24. [ContentProcessor(DisplayName = "Animated Model Processor")]
  25. public class AnimatedModelProcessor : ModelProcessor
  26. {
  27. const int MaxBones = 59;
  28. /// <summary>
  29. /// The main Process method converts an intermediate format content pipeline
  30. /// NodeContent tree to a ModelConte nt object with embedded animation data.
  31. /// </summary>
  32. public override ModelContent Process(NodeContent input, ContentProcessorContext context)
  33. {
  34. ValidateMesh(input, context, null);
  35. List<int> boneHierarchy = new List<int>();
  36. // Chain to the base ModelProcessor class so it can convert the model data.
  37. ModelContent model = base.Process(input, context);
  38. // Add each of the bones
  39. foreach (ModelBoneContent bone in model.Bones)
  40. {
  41. boneHierarchy.Add(model.Bones.IndexOf(bone.Parent as ModelBoneContent));
  42. }
  43. // Animation clips inside the object (mesh)
  44. Dictionary<string, ModelAnimationClip> animationClips = new Dictionary<string, ModelAnimationClip>();
  45. // Animation clips at the root of the object
  46. Dictionary<string, ModelAnimationClip> rootClips = new Dictionary<string, ModelAnimationClip>();
  47. // Process the animations
  48. ProcessAnimations(input, model, animationClips, rootClips);
  49. // Store the data for the model
  50. model.Tag = new ModelData(animationClips, rootClips, null, null, boneHierarchy);
  51. return model;
  52. }
  53. /// <summary>
  54. /// Converts an intermediate format content pipeline AnimationContentDictionary
  55. /// object to our runtime AnimationClip format.
  56. /// </summary>
  57. static void ProcessAnimations(
  58. NodeContent input,
  59. ModelContent model,
  60. Dictionary<string, ModelAnimationClip> animationClips,
  61. Dictionary<string, ModelAnimationClip> rootClips)
  62. {
  63. // Build up a table mapping bone names to indices.
  64. Dictionary<string, int> boneMap = new Dictionary<string, int>();
  65. for (int i = 0; i < model.Bones.Count; i++)
  66. {
  67. string boneName = model.Bones[i].Name;
  68. if (!string.IsNullOrEmpty(boneName))
  69. boneMap.Add(boneName, i);
  70. }
  71. // Convert each animation in the root of the object
  72. foreach (KeyValuePair<string, AnimationContent> animation in input.Animations)
  73. {
  74. ModelAnimationClip processed = ProcessRootAnimation(animation.Value, model.Bones[0].Name);
  75. rootClips.Add(animation.Key, processed);
  76. }
  77. // Get the unique names of the animations on the mesh children
  78. List<string> animationNames = new List<string>();
  79. AddAnimationNodes(animationNames, input);
  80. // Now create those animations
  81. foreach (string key in animationNames)
  82. {
  83. ModelAnimationClip processed = ProcessAnimation(key, boneMap, input, model);
  84. animationClips.Add(key, processed);
  85. }
  86. }
  87. static void AddAnimationNodes(List<string> animationNames, NodeContent node)
  88. {
  89. foreach (NodeContent childNode in node.Children)
  90. {
  91. // If this node doesn't have keyframes for this animation we should just skip it
  92. foreach (string key in childNode.Animations.Keys)
  93. {
  94. if (!animationNames.Contains(key))
  95. animationNames.Add(key);
  96. }
  97. AddAnimationNodes(animationNames, childNode);
  98. }
  99. }
  100. /// <summary>
  101. /// Converts an intermediate format content pipeline AnimationContent
  102. /// object to our runtime AnimationClip format.
  103. /// </summary>
  104. public static ModelAnimationClip ProcessRootAnimation(AnimationContent animation, string name)
  105. {
  106. List<ModelKeyframe> keyframes = new List<ModelKeyframe>();
  107. // The root animation is controlling the root of the bones
  108. AnimationChannel channel = animation.Channels[name];
  109. // Add the transformations on the root of the model
  110. foreach (AnimationKeyframe keyframe in channel)
  111. {
  112. keyframes.Add(new ModelKeyframe(0, keyframe.Time, keyframe.Transform));
  113. }
  114. // Sort the merged keyframes by time.
  115. keyframes.Sort(CompareKeyframeTimes);
  116. if (keyframes.Count == 0)
  117. throw new InvalidContentException("Animation has no keyframes.");
  118. if (animation.Duration <= TimeSpan.Zero)
  119. throw new InvalidContentException("Animation has a zero duration.");
  120. return new ModelAnimationClip(animation.Duration, keyframes);
  121. }
  122. /// <summary>
  123. /// Converts an intermediate format content pipeline AnimationContent
  124. /// object to our runtime AnimationClip format.
  125. /// </summary>
  126. static ModelAnimationClip ProcessAnimation(
  127. string animationName,
  128. Dictionary<string, int> boneMap,
  129. NodeContent input,
  130. ModelContent model)
  131. {
  132. List<ModelKeyframe> keyframes = new List<ModelKeyframe>();
  133. TimeSpan duration = TimeSpan.Zero;
  134. AddTransformationNodes(animationName, boneMap, input, keyframes, ref duration);
  135. // Sort the merged keyframes by time.
  136. keyframes.Sort(CompareKeyframeTimes);
  137. if (keyframes.Count == 0)
  138. throw new InvalidContentException("Animation has no keyframes.");
  139. if (duration <= TimeSpan.Zero)
  140. throw new InvalidContentException("Animation has a zero duration.");
  141. return new ModelAnimationClip(duration, keyframes);
  142. }
  143. static void AddTransformationNodes(
  144. string animationName,
  145. Dictionary<string, int> boneMap,
  146. NodeContent input,
  147. List<ModelKeyframe> keyframes,
  148. ref TimeSpan duration)
  149. {
  150. // Add the transformation on each of the meshes
  151. foreach (NodeContent childNode in input.Children)
  152. {
  153. // If this node doesn't have keyframes for this animation we should just skip it
  154. if (childNode.Animations.ContainsKey(animationName))
  155. {
  156. AnimationChannel childChannel = childNode.Animations[animationName].Channels[childNode.Name];
  157. if (childNode.Animations[animationName].Duration != duration)
  158. {
  159. if (duration < childNode.Animations[animationName].Duration)
  160. duration = childNode.Animations[animationName].Duration;
  161. }
  162. int boneIndex;
  163. if (!boneMap.TryGetValue(childNode.Name, out boneIndex))
  164. {
  165. throw new InvalidContentException(string.Format(
  166. "Found animation for bone '{0}', which is not part of the model.",
  167. childNode.Name));
  168. }
  169. foreach (AnimationKeyframe keyframe in childChannel)
  170. {
  171. keyframes.Add(new ModelKeyframe(boneIndex, keyframe.Time, keyframe.Transform));
  172. }
  173. }
  174. AddTransformationNodes(animationName, boneMap, childNode, keyframes, ref duration);
  175. }
  176. }
  177. /// <summary>
  178. /// Comparison function for sorting keyframes into ascending time order.
  179. /// </summary>
  180. static int CompareKeyframeTimes(ModelKeyframe a, ModelKeyframe b)
  181. {
  182. return a.Time.CompareTo(b.Time);
  183. }
  184. /// <summary>
  185. /// Makes sure this mesh contains the kind of data we know how to animate.
  186. /// </summary>
  187. static void ValidateMesh(NodeContent node, ContentProcessorContext context, string parentBoneName)
  188. {
  189. MeshContent mesh = node as MeshContent;
  190. if (mesh != null)
  191. {
  192. // Validate the mesh.
  193. if (parentBoneName != null)
  194. {
  195. context.Logger.LogWarning(null, null,
  196. "Mesh {0} is a child of bone {1}. AnimatedModelProcessor " +
  197. "does not correctly handle meshes that are children of bones.",
  198. mesh.Name, parentBoneName);
  199. }
  200. }
  201. else if (node is BoneContent)
  202. {
  203. // If this is a bone, remember that we are now looking inside it.
  204. parentBoneName = node.Name;
  205. }
  206. // Recurse (iterating over a copy of the child collection,
  207. // because validating children may delete some of them).
  208. foreach (NodeContent child in new List<NodeContent>(node.Children))
  209. ValidateMesh(child, context, parentBoneName);
  210. }
  211. /// <summary>
  212. /// Bakes unwanted transforms into the model geometry,
  213. /// so everything ends up in the same coordinate system.
  214. /// </summary>
  215. static void FlattenTransforms(NodeContent node, BoneContent skeleton)
  216. {
  217. foreach (NodeContent child in node.Children)
  218. {
  219. // Don't process the skeleton, because that is special.
  220. if (child == skeleton)
  221. continue;
  222. // Bake the local transform into the actual geometry.
  223. MeshHelper.TransformScene(child, child.Transform);
  224. // Having baked it, we can now set the local
  225. // coordinate system back to identity.
  226. child.Transform = Matrix.Identity;
  227. // Recurse.
  228. FlattenTransforms(child, skeleton);
  229. }
  230. }
  231. }
  232. }