Advertisement
JCBDigger

XNA Blending Animations

Apr 26th, 2011
1,019
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 24.61 KB | None | 0 0
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Based on code from Microsoft samples
  4. //-----------------------------------------------------------------------------
  5. #endregion
  6.  
  7. #region JCB Documentation
  8. //-----------------------------------------------------------------------------
  9. // Added features
  10. // - BoneAdjustments - matrix to transform the scale and location of a bone
  11. // Note
  12. //  This class needs to do a lot of work so try to keep the code efficient
  13. //  Avoid the use of Bone names, use their index instead
  14. //-----------------------------------------------------------------------------
  15. #endregion
  16.  
  17. #region Using Statements
  18. using System;
  19. using System.Collections.Generic;
  20. using Microsoft.Xna.Framework;
  21. using AssetData;
  22. #endregion
  23.  
  24. namespace Engine
  25. {
  26.     /// <summary>
  27.     /// The animation player is in charge of decoding bone position
  28.     /// matrices from an animation clip.
  29.     /// </summary>
  30.     public class AnimationPlayer
  31.     {
  32.         // Information about the currently playing animation clip.
  33.         private AnimationClip currentClipValue;
  34.         private TimeSpan currentTimeValue;
  35.         private int currentKeyframe;
  36.  
  37.         // Current animation transform matrices.
  38.         private Matrix[] boneTransforms;
  39.         private Matrix[] worldTransforms;
  40.         private Matrix[] skinTransforms;
  41.         // Bone adjustments.  These can be changed on the fly but intended to be the same each frame.
  42.         // These are intended to be used to change facial shapes or the size and shape of
  43.         // a humanoid model however I am sure there are plenty of other uses for them
  44.         private Matrix[] mtxBoneAdjustments;
  45.  
  46.         // Bone movements.  These are intended to change each frame.
  47.         // They can be used to deviate bones from their animated positions.
  48.         // Use for aiming or looking movements.
  49.         private Matrix[] mtxBoneMovements;
  50.  
  51.         // Replace Frames.  These are intended for looking and aiming where multiple bones need to
  52.         // move in conjunction with each other.  The movements are taken from a separate animation clip.
  53.         // The contents of the list contain the transforms for just the bones we want to move.
  54.  
  55.         // The list of bone tranforms which can be selected from individual frames to use for
  56.         // aiming and looking
  57.         private AnimationPart aimParts;
  58.         private AnimationPart lookParts;
  59.         // The current frame position to insert in to the animation instead of the normal one
  60.         // Fractions are used to calculate the blend between two frames.
  61.         private float currentAimFrame = -1.0f;    // -1 to disable
  62.         private float lookSideFraction = 0.0f;
  63.         private float lookUpFraction = 0.0f;
  64.         // Used each frame when a replacement is required
  65.         private Matrix currentTransform = Matrix.Identity;
  66.         // Temporary look frames (global for speed)
  67.         private Matrix look1 = Matrix.Identity;
  68.         private Matrix look2 = Matrix.Identity;
  69.         // For blending between animations
  70.         private Matrix[] previousTransforms;
  71.         private Matrix[] newTransforms;
  72.         private TimeSpan blendOverTime = TimeSpan.Zero;
  73.         // Count down to zero
  74.         private TimeSpan blendTimeRemaining = TimeSpan.Zero;
  75.  
  76.  
  77.         // Backlink to the bind pose and skeleton hierarchy data.
  78.         SkinningData skinningDataValue;
  79.  
  80.         public AnimationPlayer(SkinningData skinningData)
  81.         {
  82.             ResetSkinningData(skinningData);
  83.         }
  84.  
  85.         // Use this method to change the model animated by this player
  86.         public void ResetSkinningData(SkinningData skinningData)
  87.         {
  88.             if (skinningData == null)
  89.                 throw new ArgumentNullException("skinningData");
  90.             // Start new or reuse previous matrices if the same size
  91.             if (skinningDataValue == null ||
  92.                 skinningDataValue.BindPose.Count != skinningData.BindPose.Count)
  93.             {
  94.                 boneTransforms = new Matrix[skinningData.BindPose.Count];
  95.                 worldTransforms = new Matrix[skinningData.BindPose.Count];
  96.                 skinTransforms = new Matrix[skinningData.BindPose.Count];
  97.                 // For blending from one clip to another
  98.                 previousTransforms = new Matrix[skinningData.BindPose.Count];
  99.                 newTransforms = new Matrix[skinningData.BindPose.Count];
  100.                 // Use an array with one element per bone
  101.                 // this is for performance as these are read every update
  102.                 mtxBoneAdjustments = new Matrix[skinningData.BindPose.Count];
  103.                 mtxBoneMovements = new Matrix[skinningData.BindPose.Count];
  104.             }
  105.             skinningDataValue = skinningData;
  106.             resetBoneAdjusters(); // set each to adjust nothing Matrix.identity
  107.             resetBoneMovements(); // set each to change nothing Matrix.identity
  108.         }
  109.  
  110.         /// <summary>
  111.         /// Starts decoding the specified animation clip.
  112.         /// </summary>
  113.         public void StartClip(AnimationClip clip)
  114.         {
  115.             if (clip != null)
  116.             {
  117.  
  118.                 currentClipValue = clip;
  119.                 currentTimeValue = TimeSpan.Zero;
  120.                 currentKeyframe = 0;
  121.                 // Sync sound
  122.                 currentClipValue.ResetSoundFrame();
  123.  
  124.                 // Initialize bone transforms to the bind pose.
  125.                 skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
  126.             }
  127.         }
  128.         /// <summary>
  129.         /// Change the clip by blending from the current keyframe
  130.         /// to the first frame of the new clip over the time period specified
  131.         /// </summary>
  132.         /// <param name="toClip">Next animationClip to play</param>
  133.         /// <param name="overTime">TimeSpan.FromSeconds(0.5)</param>
  134.         public void ChangeClip(AnimationClip toClip, TimeSpan overTime)
  135.         {
  136.             // First check we have a previous clip
  137.             if (currentClipValue == null)
  138.             {
  139.                 StartClip(toClip);
  140.             }
  141.             else
  142.             {
  143.                 // If we have a clip to blend from and a clip to blend to
  144.                 if (toClip != null)
  145.                 {
  146.                     // Initialise
  147.                     TimeSpan lastTime = TimeSpan.Zero;
  148.                     // Read keyframe matrices.
  149.                     IList<Keyframe> keyframes = currentClipValue.Keyframes;
  150.                     // Are we mid way through a previous change?
  151.                     if (blendTimeRemaining <= TimeSpan.Zero)
  152.                     {
  153.                         // Current position of the bones
  154.                         skinningDataValue.BindPose.CopyTo(previousTransforms, 0);
  155.                         // Apply the animationClip bone transforms
  156.                         if (currentKeyframe >= keyframes.Count)
  157.                         {
  158.                             currentKeyframe = 0;
  159.                         }
  160.                         lastTime = keyframes[currentKeyframe].Time;
  161.                         while (currentKeyframe < keyframes.Count)
  162.                         {
  163.                             Keyframe keyframe = keyframes[currentKeyframe];
  164.  
  165.                             // Stop when we've read up to the next frame
  166.                             if (keyframe.Time > lastTime)
  167.                                 break;  // from the keyframe loop
  168.  
  169.                             lastTime = keyframe.Time;
  170.  
  171.                             previousTransforms[keyframe.Bone] = keyframe.Transform;
  172.  
  173.                             currentKeyframe++;
  174.                         }
  175.                     }
  176.                     else
  177.                     {
  178.                         // We were part way through another pose change so we need the
  179.                         // blended point using the old new and old previous
  180.                         for (int boneID = 0; boneID < newTransforms.Length; boneID++)
  181.                         {
  182.                             // Blend to the old new from the previous previous
  183.                             // Count is down so blend in reverse
  184.                             previousTransforms[boneID] = AnimationManager.Blend(
  185.                                 newTransforms[boneID],
  186.                                 previousTransforms[boneID],
  187.                                 (float)blendTimeRemaining.Ticks / (float)blendOverTime.Ticks);
  188.                         }
  189.  
  190.                     }
  191.  
  192.                     // Now change to the new clip
  193.                     currentClipValue = toClip;
  194.                     // Sync the sound
  195.                     currentClipValue.ResetSoundFrame();
  196.  
  197.                     // New position of the bones
  198.                     skinningDataValue.BindPose.CopyTo(newTransforms, 0);
  199.                     // Read keyframe matrices.
  200.                     keyframes = currentClipValue.Keyframes;
  201.                     // Get the first set of transforms
  202.                     currentKeyframe = 0;
  203.                     lastTime = keyframes[currentKeyframe].Time;
  204.                     while (currentKeyframe < keyframes.Count)
  205.                     {
  206.                         Keyframe keyframe = keyframes[currentKeyframe];
  207.  
  208.                         // Stop when we've read up to the next frame
  209.                         if (keyframe.Time > lastTime)
  210.                             break;  // from the keyframe loop
  211.  
  212.                         lastTime = keyframe.Time;
  213.  
  214.                         newTransforms[keyframe.Bone] = keyframe.Transform;
  215.  
  216.                         currentKeyframe++;
  217.                     }
  218.  
  219.                     // Ready for the start of the frame
  220.                     currentTimeValue = TimeSpan.Zero;
  221.                     currentKeyframe = 0;
  222.                     blendOverTime = overTime;
  223.                     blendTimeRemaining = overTime;
  224.                 }
  225.             }
  226.         }
  227.  
  228.  
  229.         /// <summary>
  230.         /// Advances the current animation position and returns true if a step sound
  231.         /// should be played.
  232.         /// </summary>
  233.         public bool Update(TimeSpan time, bool relativeToCurrentTime,
  234.                            Matrix rootTransform)
  235.         {
  236.             bool playSound = false;
  237.             if (currentClipValue != null)
  238.             {
  239.                 playSound = UpdateBoneTransforms(time, relativeToCurrentTime);
  240.                 UpdateWorldTransforms(rootTransform);
  241.                 UpdateSkinTransforms();
  242.             }
  243.             return playSound;
  244.         }
  245.  
  246.  
  247.         /// <summary>
  248.         /// Helper used by the Update method to refresh the BoneTransforms data.
  249.         /// Returns true if a step sound should be played
  250.         /// </summary>
  251.         public bool UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
  252.         {
  253.             bool playSound = false;
  254.             if (currentClipValue == null)
  255.             {
  256.                 System.Diagnostics.Debug.WriteLine("AnimationPlayer.Update was called before StartClip");
  257.                 return playSound;
  258.             }
  259.  
  260.             // First check if we are changing clips before playing the current clip
  261.             if (blendTimeRemaining <= TimeSpan.Zero && relativeToCurrentTime)
  262.             {
  263.                 // Update the animation position.
  264.                 // Normally the time is the elapsed time since the last update therefore it is relative
  265.                 // to the current time and has to be added on to get the total play time up to this moment
  266.                 if (relativeToCurrentTime)
  267.                 {
  268.                     time += currentTimeValue;
  269.  
  270.                     // If we reached the end, loop back to the start.
  271.                     while (time >= currentClipValue.Duration)
  272.                     {
  273.                         time -= currentClipValue.Duration;
  274.                     }
  275.                 }
  276.  
  277.                 if ((time < TimeSpan.Zero) || (time >= currentClipValue.Duration))
  278.                 {
  279.                     System.Diagnostics.Debug.WriteLine("Time value was outside limits");
  280.                     time = TimeSpan.Zero;
  281.                     currentClipValue.ResetSoundFrame();
  282.                 }
  283.  
  284.                 // If the position moved backwards we reached the end and have gone back to the start.
  285.                 // Reset the keyframe index and restart the sound sync.
  286.                 if (time < currentTimeValue)
  287.                 {
  288.                     currentKeyframe = 0;
  289.                     skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
  290.                     currentClipValue.ResetSoundFrame();
  291.                 }
  292.  
  293.                 currentTimeValue = time;
  294.  
  295.                 // Read keyframe matrices.
  296.                 IList<Keyframe> keyframes = currentClipValue.Keyframes;
  297.  
  298.                 // Apply the animationClip bone transforms
  299.                 while (currentKeyframe < keyframes.Count)
  300.                 {
  301.                     Keyframe keyframe = keyframes[currentKeyframe];
  302.  
  303.                     // Stop when we've read up to the current time position.
  304.                     // There are multiple bone transforms at each keyframe time point.
  305.                     // currentKeyframe counter is stopped at the current frame.
  306.                     if (keyframe.Time > currentTimeValue)
  307.                         break;  // from the keyframe loop
  308.  
  309.                     // Use this keyframe
  310.                     boneTransforms[keyframe.Bone] = keyframe.Transform;
  311.  
  312.                     // Remembered between updates so the next update
  313.                     // starts from where the previous left off
  314.                     currentKeyframe++;
  315.                 }
  316.             }
  317.             else
  318.             {
  319.                 // Count down
  320.                 blendTimeRemaining -= time;
  321.                 // Needed to start the new clip
  322.                 currentTimeValue = time;
  323.             }
  324.  
  325.             // Synchronise sound
  326.             if (currentTimeValue >= currentClipValue.NextSoundFrameTime())
  327.             {
  328.                 playSound = true;
  329.                 currentClipValue.IncrementSoundFrame();
  330.             }
  331.  
  332.             // Look and Aim replacement bone positions
  333.             // Keep this as fast as possible as updated frequently.
  334.             // Loop through each bone
  335.             for (int boneID = 0; boneID < boneTransforms.Length; boneID++)
  336.             {
  337.                 // Keep the default because we only change some of the bones
  338.                 currentTransform = boneTransforms[boneID];
  339.  
  340.                 // Blend to the next clip here (or from the last depending on chosen method)
  341.                 // We don't need the above default animation, if we are blending
  342.                 if (blendTimeRemaining > TimeSpan.Zero && relativeToCurrentTime)
  343.                 {
  344.                     // Count is down so blend in reverse
  345.                     currentTransform = AnimationManager.Blend(newTransforms[boneID], previousTransforms[boneID],
  346.                         (float)blendTimeRemaining.Ticks / (float)blendOverTime.Ticks);
  347.                 }
  348.  
  349.                 // Replace the default with something else (currentAimFrame = -1 of not aiming)
  350.                 if (currentAimFrame > -0.5f && aimParts != null)
  351.                 {
  352.                     int lower = (int)Math.Floor((double)currentAimFrame);
  353.                     int upper = (int)Math.Ceiling((double)currentAimFrame);
  354.                     if (lower < 0)
  355.                     {
  356.                         lower = 0;
  357.                     }
  358.                     if (upper > aimParts.TotalFrames - 1)
  359.                     {
  360.                         upper = aimParts.TotalFrames - 1;
  361.                     }
  362.  
  363.                     // If the bone exists in the list of replacements then change it
  364.                     if (aimParts.Frames[lower].transform.ContainsKey(boneID) &&
  365.                         aimParts.Frames[upper].transform.ContainsKey(boneID))
  366.                     {
  367.                         // deducting the integer lower leaves just the fraction portion
  368.                         float fraction = currentAimFrame - (float)lower;
  369.                         // Blend between the frame before and the frame after the desired pitch
  370.                         currentTransform = AnimationManager.Blend(
  371.                             aimParts.Frames[lower].transform[boneID],
  372.                             aimParts.Frames[upper].transform[boneID], fraction);
  373.                     }
  374.                 }
  375.  
  376.                 // Look side to side and up and down.
  377.                 // Side to side first frame 0 to 1 and 3 to 2
  378.                 // Then blend the results up to down
  379.                 if (lookParts != null && lookParts.Frames.Count > 3)
  380.                 {
  381.                     // Side to side first
  382.                     if (lookParts.Frames[0].transform.ContainsKey(boneID) &&
  383.                         lookParts.Frames[1].transform.ContainsKey(boneID) &&
  384.                         lookParts.Frames[2].transform.ContainsKey(boneID) &&
  385.                         lookParts.Frames[3].transform.ContainsKey(boneID))
  386.                     {
  387.                         // Top left, clockwise looking from the back of the head
  388.                         // Look from left to right
  389.                         look1 = AnimationManager.Blend(
  390.                             lookParts.Frames[0].transform[boneID],
  391.                             lookParts.Frames[1].transform[boneID], lookSideFraction);
  392.                         look2 = AnimationManager.Blend(
  393.                             lookParts.Frames[3].transform[boneID],
  394.                             lookParts.Frames[2].transform[boneID], lookSideFraction);
  395.                         // Up to down
  396.                         currentTransform = AnimationManager.Blend(
  397.                             look1, look2, lookUpFraction);
  398.  
  399.                     }
  400.  
  401.                 }
  402.                 // Apply Bone Adjustments to change the shape of a model.
  403.                 boneTransforms[boneID] = currentTransform *
  404.                     mtxBoneMovements[boneID] *
  405.                     mtxBoneAdjustments[boneID];
  406.             }
  407.             return playSound;
  408.         }
  409.  
  410.  
  411.         /// <summary>
  412.         /// Helper used by the Update method to refresh the WorldTransforms data.
  413.         /// </summary>
  414.         public void UpdateWorldTransforms(Matrix rootTransform)
  415.         {
  416.             // Root bone.
  417.             worldTransforms[0] = boneTransforms[0] * rootTransform;
  418.  
  419.             // Child bones.
  420.             for (int bone = 1; bone < worldTransforms.Length; bone++)
  421.             {
  422.                 int parentBone = skinningDataValue.SkeletonHierarchy[bone];
  423.  
  424.                 worldTransforms[bone] = boneTransforms[bone] *
  425.                                         worldTransforms[parentBone];
  426.             }
  427.         }
  428.  
  429.  
  430.         /// <summary>
  431.         /// Helper used by the Update method to refresh the SkinTransforms data.
  432.         /// </summary>
  433.         public void UpdateSkinTransforms()
  434.         {
  435.             for (int bone = 0; bone < skinTransforms.Length; bone++)
  436.             {
  437.                 skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] *
  438.                                             worldTransforms[bone];
  439.             }
  440.         }
  441.  
  442.  
  443.         /// <summary>
  444.         /// Gets the current bone transform matrices, relative to their parent bones.
  445.         /// </summary>
  446.         public Matrix[] GetBoneTransforms()
  447.         {
  448.             return boneTransforms;
  449.         }
  450.  
  451.  
  452.         /// <summary>
  453.         /// Gets the current bone transform matrices, in absolute format.
  454.         /// </summary>
  455.         public Matrix[] GetWorldTransforms()
  456.         {
  457.             return worldTransforms;
  458.         }
  459.  
  460.  
  461.         /// <summary>
  462.         /// Gets the current bone transform matrices,
  463.         /// relative to the skinning bind pose.
  464.         /// </summary>
  465.         public Matrix[] GetSkinTransforms()
  466.         {
  467.             return skinTransforms;
  468.         }
  469.  
  470.  
  471.         /// <summary>
  472.         /// Gets the clip currently being decoded.
  473.         /// </summary>
  474.         public AnimationClip CurrentClip
  475.         {
  476.             get { return currentClipValue; }
  477.         }
  478.  
  479.  
  480.         /// <summary>
  481.         /// Gets the current play position.
  482.         /// </summary>
  483.         public TimeSpan CurrentTime
  484.         {
  485.             get { return currentTimeValue; }
  486.         }
  487.  
  488.         #region Adjustments and Movements
  489.  
  490.         /// <summary>
  491.         /// Adds a BoneAdjuster which transforms bones
  492.         /// Each bone can only have one adjustment stored however as it is a matrix they
  493.         /// could be combined outside these functions and added back as one matrix
  494.         /// Can be easily changed on the fly
  495.         /// </summary>
  496.         public void addBoneAdjuster(int iBone, Matrix mtxAdjust)
  497.         {
  498.             if (iBone < mtxBoneAdjustments.Length)
  499.             {
  500.                 mtxBoneAdjustments[iBone] = mtxAdjust;
  501.             }
  502.         }
  503.  
  504.         /// <summary>
  505.         /// Removes a Bone transformation from the list of adjustments
  506.         /// </summary>
  507.         public void removeBoneAdjuster(int iBone)
  508.         {
  509.             if (iBone < mtxBoneAdjustments.Length)
  510.             {
  511.                 mtxBoneAdjustments[iBone] = Matrix.Identity;    // set back to do nothing
  512.             }
  513.         }
  514.  
  515.         /// <summary>
  516.         /// Get the bone adjustment matrix for a bone
  517.         /// </summary>
  518.         public Matrix getBoneAdjuster(int iBone)
  519.         {
  520.             return mtxBoneAdjustments[iBone];
  521.         }
  522.  
  523.         /// <summary>
  524.         /// Resets all the Bone transformation to do nothing Matrix.identity
  525.         /// </summary>
  526.         public void resetBoneAdjusters()
  527.         {
  528.             for (int iLoop = 0; iLoop < mtxBoneAdjustments.Length; iLoop++)
  529.             {
  530.                 mtxBoneAdjustments[iLoop] = Matrix.Identity;   // set each to adjust nothing
  531.             }
  532.         }
  533.  
  534.         /// <summary>
  535.         /// Gets all the current bone adjustment matrices,
  536.         /// </summary>
  537.         public Matrix[] getBoneAdjusters()
  538.         {
  539.             return mtxBoneAdjustments;
  540.         }
  541.  
  542.         /// <summary>
  543.         /// Resets all the Bone transformation to do nothing Matrix.identity
  544.         /// </summary>
  545.         public void resetBoneMovements()
  546.         {
  547.             for (int iLoop = 0; iLoop < mtxBoneMovements.Length; iLoop++)
  548.             {
  549.                 mtxBoneMovements[iLoop] = Matrix.Identity;   // set each to adjust nothing
  550.             }
  551.         }
  552.  
  553.         /// <summary>
  554.         /// To move bones to look or aim
  555.         /// </summary>
  556.         public void MoveBone(int iBone, Matrix mtxChange)
  557.         {
  558.             if (iBone < mtxBoneMovements.Length)
  559.             {
  560.                 mtxBoneMovements[iBone] = mtxChange;
  561.             }
  562.         }
  563.  
  564.         /// <summary>
  565.         /// This works in conjunction with the currentAimFrame to replace some of the bone transforms.
  566.         /// </summary>
  567.         /// <param name="clip">AnimationClip using just some bones where each frame is selected manually
  568.         /// instead of using the time reference.</param>
  569.         /// <returns>The number of frames in the clip.</returns>
  570.         public void SetAimPart(AnimationPart clip)
  571.         {
  572.             if (clip != null)
  573.             {
  574.                 aimParts = clip;
  575.             }
  576.         }
  577.  
  578.         /// <summary>
  579.         /// This works with currentLookFraction to blend between the mid position and a look
  580.         /// left or right.
  581.         /// </summary>
  582.         /// <param name="clip"></param>
  583.         public void SetLookPart(AnimationPart clip)
  584.         {
  585.             if (clip != null)
  586.             {
  587.                 lookParts = clip;
  588.             }
  589.         }
  590.  
  591.         /// <summary>
  592.         /// Any frame position from 0.0f to the max frames in the replacement clip.
  593.         /// -1 to disable replacements.
  594.         /// Fractions between frames are used to blend the frames.
  595.         /// </summary>
  596.         public void AimFramePoint(float replacement)
  597.         {
  598.             currentAimFrame = replacement;
  599.             // Make sure we don't go too far
  600.             if (aimParts != null)
  601.             {
  602.                 if (currentAimFrame > aimParts.TotalFrames - 1)
  603.                 {
  604.                     currentAimFrame = aimParts.TotalFrames - 1;
  605.                 }
  606.                 // If no clip has been set then disable replacements
  607.                 if (aimParts.TotalFrames < 1)
  608.                 {
  609.                     currentAimFrame = -1;
  610.                 }
  611.             }
  612.         }
  613.  
  614.         // used for look round
  615.         public void Look(float up, float side)
  616.         {
  617.             lookUpFraction = MathHelper.Clamp(up, 0.0f, 1.0f);
  618.             lookSideFraction = MathHelper.Clamp(side, 0.0f, 1.0f);
  619.         }
  620.  
  621.         #endregion
  622.  
  623.     }
  624.  
  625. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement