Sunday, January 17, 2016

29. Cross Fade Animation Blending Fixed


     I have fixed the cross fade function in the skeletal XNAnimation Library for XNA 4.0 & MonoGame. Cross Fade interpolates between two animation clips, fading out the current animation clip and fading in a new one. This allows the character's animations to transition more smoothly when their actions change. Animations can be played forward and backward. The animation speed can also be controlled and looped. The XNAnimation library supports models up to 80 bones and a .xml file can be used to split animation into a new set of animations based on time or keyframes. I am currently working on another technique called Additve Blending. The sample source code for cross fade animation blending is below. 


using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using XNAnimation;
using XNAnimation.Controllers;
using XNAnimation.Effects;

namespace CrossFadeBlendSample
{
    ///
    /// This sample illustrates how to use an animation controller
    ///
    public class XNAnimationSample : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;

        ///
        /// Graphics 2D stuff
        ///
        SpriteBatch spriteBatch;
        SpriteFont spriteFont;

        ///
        /// View and Projection camera matrices
        ///
        Matrix[] absoluteBoneTransforms;
        Matrix cameraView;
        Matrix cameraProjection;

        ///
        /// Stores the last keyboard state
        ///
        KeyboardState lastKeyboradState;

        ///
        /// The SkinnedModel class handles skeletal animated models
        ///
        SkinnedModel skinnedModel;

        ///
        /// Index of the active skinned model being drawed
        ///
        int activeSkinnedModelIndex = 0;

        ///
        /// An array with all the skinned model files available
        ///
        readonly String[] skinnedModelFiles = { "Models/PlayerMarine", "Models/EnemyBeast"};

        ///
        /// Custom world transformation for each model
        ///
        readonly Matrix[] worldTransformations = {Matrix.CreateTranslation(0, -12, 0),
            Matrix.Identity, Matrix.Identity};

        ///
        /// The AnimationController class handles the animation playback
        ///
        AnimationController animationController;

        ///
        /// Index of the active animation being played
        ///
        int activeAnimationClipIndex = 0;

        ///
        /// If true, enable the keyframe interpolation
        ///
        bool enableInterpolation = false;

        ///
        /// Copyright messages that appears when each model is draw
        ///
        readonly String[] copyrightMessages = {
            "Model courtesy of FloatBox Studios",
            "Model courtesy of Psionic",
            "DirectX SDK Sample Model"};

        public XNAnimationSample()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferMultiSampling = true;

            Content.RootDirectory = "Content";
        }

        private void LoadSkinnedModel()
        {
            // Loads an animated model
            skinnedModel = Content.Load<SkinnedModel>(skinnedModelFiles[activeSkinnedModelIndex]);

            // Copy the absolute transformation of each node
            absoluteBoneTransforms = new Matrix[skinnedModel.Model.Bones.Count];
            skinnedModel.Model.CopyBoneTransformsTo(absoluteBoneTransforms);

            // Creates an animation controller
            animationController = new AnimationController(skinnedModel.SkeletonBones);

            // Start the first animation stored in the AnimationClips dictionary
            animationController.StartClip(
                skinnedModel.AnimationClips.Values[activeAnimationClipIndex]);
        }

        protected override void LoadContent()
        {
            // Create a SpriteBatch and a SpritFont for 2D drawing
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Fonts/Arial");

            // Setup camera
            cameraView = Matrix.CreateLookAt(new Vector3(10, 15, 30), new Vector3(0, 12, 0), Vector3.Up);
            cameraProjection = Matrix.CreatePerspectiveFieldOfView(1, 1, 1, 1000);

            // Load a skinned model
            activeSkinnedModelIndex = 0;
            activeAnimationClipIndex = 0;

            LoadSkinnedModel();
        }


        protected override void Update(GameTime gameTime)
        {
            #region Keyboard Handling
            KeyboardState keyboradState = Keyboard.GetState();

            // Exit
            if (keyboradState.IsKeyDown(Keys.Escape))
                Exit();

            // 'Up' and 'Down' changes the animation speed
            if (keyboradState.IsKeyDown(Keys.Up))
            {
                animationController.Speed += 0.005f;
            }
            else if (keyboradState.IsKeyDown(Keys.Down))
            {
                animationController.Speed = (animationController.Speed < 0.1f) ?
                    0.1f : animationController.Speed - 0.005f;
            }

            // 'Left' and 'Right loop through the animations
            if (keyboradState.IsKeyDown(Keys.Left) && lastKeyboradState.IsKeyUp(Keys.Left))
            {
                activeAnimationClipIndex = ((activeAnimationClipIndex - 1) < 0) ?
                    skinnedModel.AnimationClips.Count - 1 : activeAnimationClipIndex - 1;
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClipIndex],
                    TimeSpan.FromMilliseconds(300));
            }
            else if (keyboradState.IsKeyDown(Keys.Right) && lastKeyboradState.IsKeyUp(Keys.Right))
            {
                activeAnimationClipIndex = (activeAnimationClipIndex + 1) % skinnedModel.AnimationClips.Count;
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClipIndex],
                    TimeSpan.FromMilliseconds(300));
            }

            // Use CAPSLOCK to cross fade through the animations
            if (keyboradState.IsKeyDown(Keys.CapsLock) && lastKeyboradState.IsKeyUp(Keys.CapsLock))
            {
                activeAnimationClipIndex = (activeAnimationClipIndex + 1) % skinnedModel.AnimationClips.Count;
                animationController.CrossFade(skinnedModel.AnimationClips.Values[activeAnimationClipIndex],
                    System.TimeSpan.FromMilliseconds(300));
            }

            // 'Enter' enable/disable animation interpolation
            if (keyboradState.IsKeyDown(Keys.Enter) && lastKeyboradState.IsKeyUp(Keys.Enter))
            {
                enableInterpolation = !enableInterpolation;

                if (enableInterpolation)
                {
                    animationController.TranslationInterpolation = InterpolationMode.Linear;
                    animationController.OrientationInterpolation = InterpolationMode.Spherical;
                    animationController.ScaleInterpolation = InterpolationMode.Linear;
                }
                else
                {
                    animationController.TranslationInterpolation = InterpolationMode.None;
                    animationController.OrientationInterpolation = InterpolationMode.None;
                    animationController.ScaleInterpolation = InterpolationMode.None;
                }
            }

            // 'Tab' changes the current animated model
            if (keyboradState.IsKeyDown(Keys.Tab) && lastKeyboradState.IsKeyUp(Keys.Tab))
            {
                activeSkinnedModelIndex = (activeSkinnedModelIndex + 1) % skinnedModelFiles.Length;
                activeAnimationClipIndex = 0;
                LoadSkinnedModel();
            }

            lastKeyboradState = keyboradState;

            #endregion

            // Update the animation according to the elapsed time
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }


        protected override void Draw(GameTime gameTime)
        {
         
            graphics.GraphicsDevice.Clear(Color.Gray);
            graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            graphics.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            graphics.GraphicsDevice.BlendState = BlendState.Opaque;

            // In this first version of the library the animated model is draw
            // through an internal Model object
            foreach (ModelMesh modelMesh in skinnedModel.Model.Meshes)
            {
                foreach (SkinnedEffect effect in modelMesh.Effects)
                {
                    // Set the animated bones to the model
                    effect.SetBoneTransforms(animationController.SkinnedBoneTransforms);

                    // OPTIONAL - Configure Material
                    effect.DiffuseColor = new Vector3(0.8f);
                    effect.SpecularColor = new Vector3(0.3f);
                    effect.SpecularPower = 8;

                    // OPTIONAL - Configure lights
                    effect.EnableDefaultLighting();
                    effect.AmbientLightColor = new Vector3(0.1f);
                    effect.PreferPerPixelLighting = true;

                    // Setup camera properties
                    effect.View = cameraView;
                    effect.Projection = cameraProjection;
                }

                // Draw a model mesh
                modelMesh.Draw();
            }
         
            base.Draw(gameTime);

            Draw2D();
        }

        #region Draw 2D Stuff
        private void Draw2D()
        {
            spriteBatch.Begin();

            spriteBatch.DrawString(spriteFont, "Use [LEFT/RIGHT] to Cross Fade Blend with a new animation clip.",
                new Vector2(10, 5), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [UP/DOWN] to change the animation speed.",
                new Vector2(10, 22), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [ENTER] to enable/disable animation interpolation.",
                new Vector2(10, 39), Color.White);
            spriteBatch.DrawString(spriteFont, "Use [TAB] to change the animated model.",
                new Vector2(10, 56), Color.White);

            spriteBatch.DrawString(spriteFont, copyrightMessages[activeSkinnedModelIndex],
                new Vector2(560, 580), Color.White);
            spriteBatch.End();

        }
        #endregion

        #region Entry Point
        static void Main(string[] args)
        {
            using (XNAnimationSample game = new XNAnimationSample())
            {
                game.Run();
            }
        }
        #endregion
    }
}


No comments:

Post a Comment