Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #region File Description
- //-----------------------------------------------------------------------------
- // Based on code from Microsoft samples
- //-----------------------------------------------------------------------------
- #endregion
- #region JCB Documentation
- //-----------------------------------------------------------------------------
- // Added features
- // - BoneAdjustments - matrix to transform the scale and location of a bone
- // Note
- // This class needs to do a lot of work so try to keep the code efficient
- // Avoid the use of Bone names, use their index instead
- //-----------------------------------------------------------------------------
- #endregion
- #region Using Statements
- using System;
- using System.Collections.Generic;
- using Microsoft.Xna.Framework;
- using AssetData;
- #endregion
- namespace Engine
- {
- /// <summary>
- /// The animation player is in charge of decoding bone position
- /// matrices from an animation clip.
- /// </summary>
- public class AnimationPlayer
- {
- // Information about the currently playing animation clip.
- private AnimationClip currentClipValue;
- private TimeSpan currentTimeValue;
- private int currentKeyframe;
- // Current animation transform matrices.
- private Matrix[] boneTransforms;
- private Matrix[] worldTransforms;
- private Matrix[] skinTransforms;
- // Bone adjustments. These can be changed on the fly but intended to be the same each frame.
- // These are intended to be used to change facial shapes or the size and shape of
- // a humanoid model however I am sure there are plenty of other uses for them
- private Matrix[] mtxBoneAdjustments;
- // Bone movements. These are intended to change each frame.
- // They can be used to deviate bones from their animated positions.
- // Use for aiming or looking movements.
- private Matrix[] mtxBoneMovements;
- // Replace Frames. These are intended for looking and aiming where multiple bones need to
- // move in conjunction with each other. The movements are taken from a separate animation clip.
- // The contents of the list contain the transforms for just the bones we want to move.
- // The list of bone tranforms which can be selected from individual frames to use for
- // aiming and looking
- private AnimationPart aimParts;
- private AnimationPart lookParts;
- // The current frame position to insert in to the animation instead of the normal one
- // Fractions are used to calculate the blend between two frames.
- private float currentAimFrame = -1.0f; // -1 to disable
- private float lookSideFraction = 0.0f;
- private float lookUpFraction = 0.0f;
- // Used each frame when a replacement is required
- private Matrix currentTransform = Matrix.Identity;
- // Temporary look frames (global for speed)
- private Matrix look1 = Matrix.Identity;
- private Matrix look2 = Matrix.Identity;
- // For blending between animations
- private Matrix[] previousTransforms;
- private Matrix[] newTransforms;
- private TimeSpan blendOverTime = TimeSpan.Zero;
- // Count down to zero
- private TimeSpan blendTimeRemaining = TimeSpan.Zero;
- // Backlink to the bind pose and skeleton hierarchy data.
- SkinningData skinningDataValue;
- public AnimationPlayer(SkinningData skinningData)
- {
- ResetSkinningData(skinningData);
- }
- // Use this method to change the model animated by this player
- public void ResetSkinningData(SkinningData skinningData)
- {
- if (skinningData == null)
- throw new ArgumentNullException("skinningData");
- // Start new or reuse previous matrices if the same size
- if (skinningDataValue == null ||
- skinningDataValue.BindPose.Count != skinningData.BindPose.Count)
- {
- boneTransforms = new Matrix[skinningData.BindPose.Count];
- worldTransforms = new Matrix[skinningData.BindPose.Count];
- skinTransforms = new Matrix[skinningData.BindPose.Count];
- // For blending from one clip to another
- previousTransforms = new Matrix[skinningData.BindPose.Count];
- newTransforms = new Matrix[skinningData.BindPose.Count];
- // Use an array with one element per bone
- // this is for performance as these are read every update
- mtxBoneAdjustments = new Matrix[skinningData.BindPose.Count];
- mtxBoneMovements = new Matrix[skinningData.BindPose.Count];
- }
- skinningDataValue = skinningData;
- resetBoneAdjusters(); // set each to adjust nothing Matrix.identity
- resetBoneMovements(); // set each to change nothing Matrix.identity
- }
- /// <summary>
- /// Starts decoding the specified animation clip.
- /// </summary>
- public void StartClip(AnimationClip clip)
- {
- if (clip != null)
- {
- currentClipValue = clip;
- currentTimeValue = TimeSpan.Zero;
- currentKeyframe = 0;
- // Sync sound
- currentClipValue.ResetSoundFrame();
- // Initialize bone transforms to the bind pose.
- skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
- }
- }
- /// <summary>
- /// Change the clip by blending from the current keyframe
- /// to the first frame of the new clip over the time period specified
- /// </summary>
- /// <param name="toClip">Next animationClip to play</param>
- /// <param name="overTime">TimeSpan.FromSeconds(0.5)</param>
- public void ChangeClip(AnimationClip toClip, TimeSpan overTime)
- {
- // First check we have a previous clip
- if (currentClipValue == null)
- {
- StartClip(toClip);
- }
- else
- {
- // If we have a clip to blend from and a clip to blend to
- if (toClip != null)
- {
- // Initialise
- TimeSpan lastTime = TimeSpan.Zero;
- // Read keyframe matrices.
- IList<Keyframe> keyframes = currentClipValue.Keyframes;
- // Are we mid way through a previous change?
- if (blendTimeRemaining <= TimeSpan.Zero)
- {
- // Current position of the bones
- skinningDataValue.BindPose.CopyTo(previousTransforms, 0);
- // Apply the animationClip bone transforms
- if (currentKeyframe >= keyframes.Count)
- {
- currentKeyframe = 0;
- }
- lastTime = keyframes[currentKeyframe].Time;
- while (currentKeyframe < keyframes.Count)
- {
- Keyframe keyframe = keyframes[currentKeyframe];
- // Stop when we've read up to the next frame
- if (keyframe.Time > lastTime)
- break; // from the keyframe loop
- lastTime = keyframe.Time;
- previousTransforms[keyframe.Bone] = keyframe.Transform;
- currentKeyframe++;
- }
- }
- else
- {
- // We were part way through another pose change so we need the
- // blended point using the old new and old previous
- for (int boneID = 0; boneID < newTransforms.Length; boneID++)
- {
- // Blend to the old new from the previous previous
- // Count is down so blend in reverse
- previousTransforms[boneID] = AnimationManager.Blend(
- newTransforms[boneID],
- previousTransforms[boneID],
- (float)blendTimeRemaining.Ticks / (float)blendOverTime.Ticks);
- }
- }
- // Now change to the new clip
- currentClipValue = toClip;
- // Sync the sound
- currentClipValue.ResetSoundFrame();
- // New position of the bones
- skinningDataValue.BindPose.CopyTo(newTransforms, 0);
- // Read keyframe matrices.
- keyframes = currentClipValue.Keyframes;
- // Get the first set of transforms
- currentKeyframe = 0;
- lastTime = keyframes[currentKeyframe].Time;
- while (currentKeyframe < keyframes.Count)
- {
- Keyframe keyframe = keyframes[currentKeyframe];
- // Stop when we've read up to the next frame
- if (keyframe.Time > lastTime)
- break; // from the keyframe loop
- lastTime = keyframe.Time;
- newTransforms[keyframe.Bone] = keyframe.Transform;
- currentKeyframe++;
- }
- // Ready for the start of the frame
- currentTimeValue = TimeSpan.Zero;
- currentKeyframe = 0;
- blendOverTime = overTime;
- blendTimeRemaining = overTime;
- }
- }
- }
- /// <summary>
- /// Advances the current animation position and returns true if a step sound
- /// should be played.
- /// </summary>
- public bool Update(TimeSpan time, bool relativeToCurrentTime,
- Matrix rootTransform)
- {
- bool playSound = false;
- if (currentClipValue != null)
- {
- playSound = UpdateBoneTransforms(time, relativeToCurrentTime);
- UpdateWorldTransforms(rootTransform);
- UpdateSkinTransforms();
- }
- return playSound;
- }
- /// <summary>
- /// Helper used by the Update method to refresh the BoneTransforms data.
- /// Returns true if a step sound should be played
- /// </summary>
- public bool UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
- {
- bool playSound = false;
- if (currentClipValue == null)
- {
- System.Diagnostics.Debug.WriteLine("AnimationPlayer.Update was called before StartClip");
- return playSound;
- }
- // First check if we are changing clips before playing the current clip
- if (blendTimeRemaining <= TimeSpan.Zero && relativeToCurrentTime)
- {
- // Update the animation position.
- // Normally the time is the elapsed time since the last update therefore it is relative
- // to the current time and has to be added on to get the total play time up to this moment
- if (relativeToCurrentTime)
- {
- time += currentTimeValue;
- // If we reached the end, loop back to the start.
- while (time >= currentClipValue.Duration)
- {
- time -= currentClipValue.Duration;
- }
- }
- if ((time < TimeSpan.Zero) || (time >= currentClipValue.Duration))
- {
- System.Diagnostics.Debug.WriteLine("Time value was outside limits");
- time = TimeSpan.Zero;
- currentClipValue.ResetSoundFrame();
- }
- // If the position moved backwards we reached the end and have gone back to the start.
- // Reset the keyframe index and restart the sound sync.
- if (time < currentTimeValue)
- {
- currentKeyframe = 0;
- skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
- currentClipValue.ResetSoundFrame();
- }
- currentTimeValue = time;
- // Read keyframe matrices.
- IList<Keyframe> keyframes = currentClipValue.Keyframes;
- // Apply the animationClip bone transforms
- while (currentKeyframe < keyframes.Count)
- {
- Keyframe keyframe = keyframes[currentKeyframe];
- // Stop when we've read up to the current time position.
- // There are multiple bone transforms at each keyframe time point.
- // currentKeyframe counter is stopped at the current frame.
- if (keyframe.Time > currentTimeValue)
- break; // from the keyframe loop
- // Use this keyframe
- boneTransforms[keyframe.Bone] = keyframe.Transform;
- // Remembered between updates so the next update
- // starts from where the previous left off
- currentKeyframe++;
- }
- }
- else
- {
- // Count down
- blendTimeRemaining -= time;
- // Needed to start the new clip
- currentTimeValue = time;
- }
- // Synchronise sound
- if (currentTimeValue >= currentClipValue.NextSoundFrameTime())
- {
- playSound = true;
- currentClipValue.IncrementSoundFrame();
- }
- // Look and Aim replacement bone positions
- // Keep this as fast as possible as updated frequently.
- // Loop through each bone
- for (int boneID = 0; boneID < boneTransforms.Length; boneID++)
- {
- // Keep the default because we only change some of the bones
- currentTransform = boneTransforms[boneID];
- // Blend to the next clip here (or from the last depending on chosen method)
- // We don't need the above default animation, if we are blending
- if (blendTimeRemaining > TimeSpan.Zero && relativeToCurrentTime)
- {
- // Count is down so blend in reverse
- currentTransform = AnimationManager.Blend(newTransforms[boneID], previousTransforms[boneID],
- (float)blendTimeRemaining.Ticks / (float)blendOverTime.Ticks);
- }
- // Replace the default with something else (currentAimFrame = -1 of not aiming)
- if (currentAimFrame > -0.5f && aimParts != null)
- {
- int lower = (int)Math.Floor((double)currentAimFrame);
- int upper = (int)Math.Ceiling((double)currentAimFrame);
- if (lower < 0)
- {
- lower = 0;
- }
- if (upper > aimParts.TotalFrames - 1)
- {
- upper = aimParts.TotalFrames - 1;
- }
- // If the bone exists in the list of replacements then change it
- if (aimParts.Frames[lower].transform.ContainsKey(boneID) &&
- aimParts.Frames[upper].transform.ContainsKey(boneID))
- {
- // deducting the integer lower leaves just the fraction portion
- float fraction = currentAimFrame - (float)lower;
- // Blend between the frame before and the frame after the desired pitch
- currentTransform = AnimationManager.Blend(
- aimParts.Frames[lower].transform[boneID],
- aimParts.Frames[upper].transform[boneID], fraction);
- }
- }
- // Look side to side and up and down.
- // Side to side first frame 0 to 1 and 3 to 2
- // Then blend the results up to down
- if (lookParts != null && lookParts.Frames.Count > 3)
- {
- // Side to side first
- if (lookParts.Frames[0].transform.ContainsKey(boneID) &&
- lookParts.Frames[1].transform.ContainsKey(boneID) &&
- lookParts.Frames[2].transform.ContainsKey(boneID) &&
- lookParts.Frames[3].transform.ContainsKey(boneID))
- {
- // Top left, clockwise looking from the back of the head
- // Look from left to right
- look1 = AnimationManager.Blend(
- lookParts.Frames[0].transform[boneID],
- lookParts.Frames[1].transform[boneID], lookSideFraction);
- look2 = AnimationManager.Blend(
- lookParts.Frames[3].transform[boneID],
- lookParts.Frames[2].transform[boneID], lookSideFraction);
- // Up to down
- currentTransform = AnimationManager.Blend(
- look1, look2, lookUpFraction);
- }
- }
- // Apply Bone Adjustments to change the shape of a model.
- boneTransforms[boneID] = currentTransform *
- mtxBoneMovements[boneID] *
- mtxBoneAdjustments[boneID];
- }
- return playSound;
- }
- /// <summary>
- /// Helper used by the Update method to refresh the WorldTransforms data.
- /// </summary>
- public void UpdateWorldTransforms(Matrix rootTransform)
- {
- // Root bone.
- worldTransforms[0] = boneTransforms[0] * rootTransform;
- // Child bones.
- for (int bone = 1; bone < worldTransforms.Length; bone++)
- {
- int parentBone = skinningDataValue.SkeletonHierarchy[bone];
- worldTransforms[bone] = boneTransforms[bone] *
- worldTransforms[parentBone];
- }
- }
- /// <summary>
- /// Helper used by the Update method to refresh the SkinTransforms data.
- /// </summary>
- public void UpdateSkinTransforms()
- {
- for (int bone = 0; bone < skinTransforms.Length; bone++)
- {
- skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] *
- worldTransforms[bone];
- }
- }
- /// <summary>
- /// Gets the current bone transform matrices, relative to their parent bones.
- /// </summary>
- public Matrix[] GetBoneTransforms()
- {
- return boneTransforms;
- }
- /// <summary>
- /// Gets the current bone transform matrices, in absolute format.
- /// </summary>
- public Matrix[] GetWorldTransforms()
- {
- return worldTransforms;
- }
- /// <summary>
- /// Gets the current bone transform matrices,
- /// relative to the skinning bind pose.
- /// </summary>
- public Matrix[] GetSkinTransforms()
- {
- return skinTransforms;
- }
- /// <summary>
- /// Gets the clip currently being decoded.
- /// </summary>
- public AnimationClip CurrentClip
- {
- get { return currentClipValue; }
- }
- /// <summary>
- /// Gets the current play position.
- /// </summary>
- public TimeSpan CurrentTime
- {
- get { return currentTimeValue; }
- }
- #region Adjustments and Movements
- /// <summary>
- /// Adds a BoneAdjuster which transforms bones
- /// Each bone can only have one adjustment stored however as it is a matrix they
- /// could be combined outside these functions and added back as one matrix
- /// Can be easily changed on the fly
- /// </summary>
- public void addBoneAdjuster(int iBone, Matrix mtxAdjust)
- {
- if (iBone < mtxBoneAdjustments.Length)
- {
- mtxBoneAdjustments[iBone] = mtxAdjust;
- }
- }
- /// <summary>
- /// Removes a Bone transformation from the list of adjustments
- /// </summary>
- public void removeBoneAdjuster(int iBone)
- {
- if (iBone < mtxBoneAdjustments.Length)
- {
- mtxBoneAdjustments[iBone] = Matrix.Identity; // set back to do nothing
- }
- }
- /// <summary>
- /// Get the bone adjustment matrix for a bone
- /// </summary>
- public Matrix getBoneAdjuster(int iBone)
- {
- return mtxBoneAdjustments[iBone];
- }
- /// <summary>
- /// Resets all the Bone transformation to do nothing Matrix.identity
- /// </summary>
- public void resetBoneAdjusters()
- {
- for (int iLoop = 0; iLoop < mtxBoneAdjustments.Length; iLoop++)
- {
- mtxBoneAdjustments[iLoop] = Matrix.Identity; // set each to adjust nothing
- }
- }
- /// <summary>
- /// Gets all the current bone adjustment matrices,
- /// </summary>
- public Matrix[] getBoneAdjusters()
- {
- return mtxBoneAdjustments;
- }
- /// <summary>
- /// Resets all the Bone transformation to do nothing Matrix.identity
- /// </summary>
- public void resetBoneMovements()
- {
- for (int iLoop = 0; iLoop < mtxBoneMovements.Length; iLoop++)
- {
- mtxBoneMovements[iLoop] = Matrix.Identity; // set each to adjust nothing
- }
- }
- /// <summary>
- /// To move bones to look or aim
- /// </summary>
- public void MoveBone(int iBone, Matrix mtxChange)
- {
- if (iBone < mtxBoneMovements.Length)
- {
- mtxBoneMovements[iBone] = mtxChange;
- }
- }
- /// <summary>
- /// This works in conjunction with the currentAimFrame to replace some of the bone transforms.
- /// </summary>
- /// <param name="clip">AnimationClip using just some bones where each frame is selected manually
- /// instead of using the time reference.</param>
- /// <returns>The number of frames in the clip.</returns>
- public void SetAimPart(AnimationPart clip)
- {
- if (clip != null)
- {
- aimParts = clip;
- }
- }
- /// <summary>
- /// This works with currentLookFraction to blend between the mid position and a look
- /// left or right.
- /// </summary>
- /// <param name="clip"></param>
- public void SetLookPart(AnimationPart clip)
- {
- if (clip != null)
- {
- lookParts = clip;
- }
- }
- /// <summary>
- /// Any frame position from 0.0f to the max frames in the replacement clip.
- /// -1 to disable replacements.
- /// Fractions between frames are used to blend the frames.
- /// </summary>
- public void AimFramePoint(float replacement)
- {
- currentAimFrame = replacement;
- // Make sure we don't go too far
- if (aimParts != null)
- {
- if (currentAimFrame > aimParts.TotalFrames - 1)
- {
- currentAimFrame = aimParts.TotalFrames - 1;
- }
- // If no clip has been set then disable replacements
- if (aimParts.TotalFrames < 1)
- {
- currentAimFrame = -1;
- }
- }
- }
- // used for look round
- public void Look(float up, float side)
- {
- lookUpFraction = MathHelper.Clamp(up, 0.0f, 1.0f);
- lookSideFraction = MathHelper.Clamp(side, 0.0f, 1.0f);
- }
- #endregion
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement