Thursday, June 5, 2014

16. Import multiple animations from a single .fbx file for XNA 4.0 Refresh

How to extend the XNAnimation library for XNA 4.0. In the XNAnimation Pipeline, open up SkinnedModelImporter.cs and replace it with the following source code below.

Source Code Below:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Design;


// The type to import.
using TImport = Microsoft.Xna.Framework.Content.Pipeline.Graphics.NodeContent;

// Change the namespace to suit your project
namespace SkinnedModelPipeline
{
    [ContentImporter(".fbx", DisplayName = "Multi-take FBX Importer", DefaultProcessor = "")]
    public class SkinnedModelImporter : FbxImporter
    {
        private List<string> _animfiles;
        private List<string> _fbxheader;
        private TImport _master;
        private ContentImporterContext _context;

        public override TImport Import(string filename, ContentImporterContext context)
        {
            _context = context;

            // _animfiles will contain list of new temp anim files.
            _animfiles = new List<string>();

            // Decouple header and animation data.
            ExtractAnimations(filename);

            // Process master file (this will also process the first animation)
            _master = base.Import(filename, context);

            // Process the remaining animations.
            foreach (string file in _animfiles)
            {
                TImport anim = base.Import(file, context);

                // Append animation to master NodeContent.
                AppendAnimation(_master, anim);
            }

            // Delete the temporary animation files.
            DeleteTempFiles();

            return _master;
        }

        private void AppendAnimation(NodeContent masternode, NodeContent animnode)
        {
            foreach (KeyValuePair<string, AnimationContent> anim in animnode.Animations)
            {
                if (!masternode.Animations.ContainsKey(anim.Key))
                {
                    masternode.Animations.Add(anim.Key, anim.Value);
                }
                else
                {
                    //overwrite the animation that was stored inside the
                    //master file because it is of the wrong length (except the first animation).
                    masternode.Animations[anim.Key] = anim.Value;
                }
            }

            //foreach (NodeContent child in animnode.Children) {
            //    if (child != null) {
            //        AppendAnimation(child);
            //    }
            //}

            for (int i = 0; i < masternode.Children.Count; i++)
            {
                if (animnode.Children[i] != null)
                {
                    AppendAnimation(masternode.Children[i], animnode.Children[i]);
                }
            }
        }

        private void ExtractAnimations(string filename)
        {
            List<string> masterFile = File.ReadAllLines(filename).ToList();
            string path = Path.GetDirectoryName(filename);
            int open_idx = 0,
                length,
                num_open = -1,
                filenum = 0;
            bool foundTake = false;

            int idx = masterFile.IndexOf("Takes:  {") + 1;
            _fbxheader = masterFile.Take(idx).ToList();
            List<string> anims = masterFile.Skip(idx).ToList();

            // Extract each animation and create a temporary anim file.
            for (int i = 0; i < anims.Count; i++)
            {
                if (anims[i].Contains("Take: "))
                {
                    open_idx = i;
                    num_open = 0;
                    foundTake = true;
                }

                if (anims[i].Contains("{") &&
                    foundTake)
                {
                    num_open++;
                }

                if (anims[i].Contains("}") &&
                    foundTake)
                {
                    num_open--;
                }

                if (num_open == 0 &&
                    foundTake)
                {
                    // Skip first animation since this is processed in the master
                    // fbx file.
                    if (filenum > 0)
                    {
                        length = i - open_idx + 1;

                        // Create temp file from header + anim data.
                        CreateTempFile(Path.Combine(path, "tmp.anim." + filenum + ".fbx"),
                                       anims.Skip(open_idx).Take(length).ToArray());
                    }
                    filenum++;
                    foundTake = false;
                }
            }
        }

        private void CreateTempFile(string filename, string[] data)
        {
            List<string> completefile = new List<string>();
            completefile.AddRange(_fbxheader);
            completefile.AddRange(data);

            try
            {
                // Write data to new temp file.
                File.WriteAllLines(filename, completefile.ToArray());

                // Store temp file name for processing.
                _animfiles.Add(filename);
            }
            catch
            {
                // Error while creating temp file.
                _context.Logger.LogWarning(null, null, "Error creating temp file: {0}", filename);
            }
        }

        private void DeleteTempFiles()
        {
            foreach (string file in _animfiles)
            {
                File.Delete(file);
            }
        }
    }
}

Monday, May 26, 2014

15: Water Shader




Water is one of the most complex shaders to render in games. I am pleased to announce that I was able to implement an ocean shader with wave ripples and light reflecting on the surface.

Friday, May 2, 2014

#14: Attaching Non Skinned Models to an Animated Model








       Models can now be attached to animated characters and they don't necessarily have to be rigged. I am using the XNAnimation Library. I got it to work in XNA 4.0 by creating a merge content processor. In the future, this will work great for character customization and vehicle customization. I will soon make the entire menu system into a 3D scene. The camera will transition to different areas of the 3D scene after the player make certain selections.

Example source code below:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using XNAnimation;
using XNAnimation.Controllers;
using System;

namespace ModelAttachment
{

public class ModelAttachment : Microsoft.Xna.Framework.Game
{
        int RIGHT_HAND_BONE_ID;
        /// <summary>
        /// The AnimationController class handles the animation playback
        /// </summary>
        AnimationController animationController;

        SkinnedModel marine;
        Matrix view;
        Matrix projection;

        Model gun;
        
        public override void LoadContent()
        {
              RIGHT_HAND_BONE_ID = 15;
              // Calculate camera view and projection matrices.
              view = Matrix.CreateLookAt(new Vector3(-5, 5, -30),
              new Vector3(1, 3, 0), Vector3.Up);
              projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,GraphicsDevice.Viewport.AspectRatio, 10, 10000); 

           LoadMarineModel();                                                    
        }

        /// <summary>
        /// Unloads graphics content for this screen.
        /// </summary>
        public override void UnloadContent()
        {
           content.Unload();
        }

        public string idle = "Idle";

        private void LoadMarineModel()
        {
            gun = content.Load<Model>("Models/weapon");
            marine = content.Load<SkinnedModel>("Models/PlayerMarine");
            animationController = new AnimationController(marine.SkeletonBones);
            animationController.StartClip(marine.AnimationClips["Idle"]);
  }
        public override void Update(GameTime gameTime)
        {
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

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

            base.Update(gameTime);
        }


        /// <summary>
        /// Draws the background screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
            Drawmarine(marine, view, projection);
DrawGun(gun,view, projection, animationController.SkinnedBoneTransforms[RIGHT_HAND_BONE_ID];

        } 

        /// <summary>
        /// Draws the marine model
        /// </summary>       
        private void Drawmarine(SkinnedModel marine, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in marine.Model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    foreach (SkinnedEffect effect in mesh.Effects)
                     {               
                     effect.SetBoneTransforms(animationController.SkinnedBoneTransforms);
                        effect.World = Matrix.Identity;

                        // OPTIONAL - Configure lights
                        effect.EnableDefaultLighting();
                        effect.AmbientLightColor = new Vector3(0.1f);
                        effect.DiffuseColor = new Vector3(0.8f);
                        effect.SpecularColor = new Vector3(0.3f);
                        effect.SpecularPower = 8;

                     // Camera Properties
                        effect.View = view;
                        effect.Projection = projection;
                     }
                }
                mesh.Draw();
            }
        }
     

        public void DrawGun(Model model, Matrix view, Matrix projection, Matrix world)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            foreach (ModelMesh mm in model.Meshes)
            {
                foreach (BasicEffect be in mm.Effects)
                {
                    be.View = view;
                    be.Projection = projection;
                    be.World = transforms[mm.ParentBone.Index] * world;
                    be.EnableDefaultLighting();
                    be.AmbientLightColor = new Vector3(0.1f);   
                    be.SpecularPower = 5;
                }
                mm.Draw();
          }
        }
      }
    }

Friday, April 18, 2014

#13: Muzzle Flash and Blood Splatter Effects



       This is a video demonstration of the muzzle flash and blood splatter effect I recently got working in the game. The game engine is built upon the XNA 4.0 Framework and MonoGame. I have also attached the temporary gun model to the sample marine character. The blood splatter and muzzle flash effect is an animated billboard. A billboard is simply a 2D sprite rendered in 3D space. This is a fake way of rendering complex effects and models. If the camera moves to view the blood splatter effects' sides as of now, it can no longer be read. Now I just have to rotate the animated blood splatter sprites so that they will always face toward the camera. This is so the blood splatter effect will appear fully 3d. Thanks for watching and I look forward to posting my next progress video update.

Sunday, November 3, 2013

#12: Cyclone Game Engine Update 3



       Sorry for not posting videos for a while. This is not the latest progress video but an older one. This is a short video showing that I successfully created a working 2D/ 3D menu system with the help of the game state management's screen manager. I also got the menu system capable of displaying splash screens with transition effects upon the start-up of the game. The menu system also supports 3D models within it and description text when you hover over different menu items. I also got a sky-box functioning in the game. This video also shows the lens flare effect functioning and placed over the sun on the sky-box.  Its tricky when the program is drawing 2d and 3d together on the same screen under the 'Draw Method'.
       Thanks to Shawn Hargreaves who provided the most efficient way to go in his blog post, I was able to fix my skybox. When I was mixing 3D rendering with 2D objects using SpriteBatch, I may noticed that my 3D graphics no longer draw correctly after you have rendered sprites. All of the models triangles and polygons appeared to be disoriented and stretched leaving tons of gaps and holes. This is because the SpriteBatch changes several device renderstates. At this stage the actual game is still in Pre-Production Phase of development and I won't be revealing too much until the game is near completion. For now, I am improving the game engine and plan to post more progress footage of it. To see more progress, visit the Steel Cyclone Studio's Facebook Page at: https://www.facebook.com/SteelCycloneStudios

Thanks and I greatly appreciate your support :-D

Monday, August 5, 2013

#11: Lens Flare Effect


The Cyclone Game Engine can now render a lens flare effect. It uses hardware occlusion queries to efficiently detect whether or not the sun is hidden behind the landscape. This will allow me to fade out the sun when the sun is not visible. I did not use my lens flare as a draw-able game component since it caused many problems. 


By changing the light direction vector I was able to position the lens flare over the sun on the sky-box. It took a while to position it just right but I eventually got it.