Browse Source

Initial Check-in

Simon (darkside) Jackson 5 years ago
parent
commit
a830adc4de

+ 92 - 0
.gitignore

@@ -0,0 +1,92 @@
+#OS junk files
+[Tt]humbs.db
+*.DS_Store
+
+#Output Linux Installer
+Installers/Linux/tmp_deb/
+Installers/Linux/tmp_run/
+*.run
+*.deb
+
+#Visual Studio files
+*.pidb
+*.userprefs
+*.[Oo]bj
+*.exe
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*.vssscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.[Cc]ache
+*.ilk
+*.log
+*.lib
+*.sbr
+*.sdf
+*.csproj.csdat
+ipch/
+obj/
+[Bb]in
+[Dd]ebug*/
+[Rr]elease*/
+Ankh.NoLoad
+.vs/
+project.lock.json
+/MonoGame.Framework/MonoGame.Framework.Net.WindowsUniversal.project.lock.json
+/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.project.lock.json
+artifacts/
+
+# JetBrains Rider
+.idea/
+
+#Tooling
+_ReSharper*/
+*.resharper
+[Tt]est[Rr]esult*
+
+#Visual Studio Rebracer extension, allows the user to automatically change the style configuration by project
+rebracer.xml
+
+#Subversion files
+.svn
+
+# Office Temp Files
+~$*
+
+#monodroid private beta
+monodroid*.msi
+
+#Unix temporary files
+*~
+
+# Output docs
+Documentation/Output/
+
+#Mac Package Files
+*.pkg
+*.mpack
+**/packages
+
+#Nuget Packages
+**/*.nupkg
+
+#Zip files
+*.zip
+
+Installers/MacOS/Scripts/Framework/postinstall
+IDE/MonoDevelop/MonoDevelop.MonoGame/templates/Common/MonoGame.Framework.dll.config
+
+# CAKE
+.cake/**
+
+# docfx
+_*

+ 7 - 0
ReadMe.md

@@ -0,0 +1,7 @@
+# MonoGame Skinned Model example
+
+Upgraded Skinned Model example from the XNA Game Studio Archive
+
+Source:
+
+https://github.com/simondarksidej/XNAGameStudio/wiki/Skinned-Model

+ 57 - 0
SkinnedModel/AnimationClip.cs

@@ -0,0 +1,57 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// AnimationClip.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Content;
+#endregion
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// An animation clip is the runtime equivalent of the
+    /// Microsoft.Xna.Framework.Content.Pipeline.Graphics.AnimationContent type.
+    /// It holds all the keyframes needed to describe a single animation.
+    /// </summary>
+    public class AnimationClip
+    {
+        /// <summary>
+        /// Constructs a new animation clip object.
+        /// </summary>
+        public AnimationClip(TimeSpan duration, List<Keyframe> keyframes)
+        {
+            Duration = duration;
+            Keyframes = keyframes;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private AnimationClip()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets the total length of the animation.
+        /// </summary>
+        [ContentSerializer]
+        public TimeSpan Duration { get; private set; }
+
+
+        /// <summary>
+        /// Gets a combined list containing all the keyframes for all bones,
+        /// sorted by time.
+        /// </summary>
+        [ContentSerializer]
+        public List<Keyframe> Keyframes { get; private set; }
+    }
+}

+ 218 - 0
SkinnedModel/AnimationPlayer.cs

@@ -0,0 +1,218 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// AnimationPlayer.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+#endregion
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// The animation player is in charge of decoding bone position
+    /// matrices from an animation clip.
+    /// </summary>
+    public class AnimationPlayer
+    {
+        #region Fields
+
+
+        // Information about the currently playing animation clip.
+        AnimationClip currentClipValue;
+        TimeSpan currentTimeValue;
+        int currentKeyframe;
+
+
+        // Current animation transform matrices.
+        Matrix[] boneTransforms;
+        Matrix[] worldTransforms;
+        Matrix[] skinTransforms;
+
+
+        // Backlink to the bind pose and skeleton hierarchy data.
+        SkinningData skinningDataValue;
+
+
+        #endregion
+
+
+        /// <summary>
+        /// Constructs a new animation player.
+        /// </summary>
+        public AnimationPlayer(SkinningData skinningData)
+        {
+            if (skinningData == null)
+                throw new ArgumentNullException("skinningData");
+
+            skinningDataValue = skinningData;
+
+            boneTransforms = new Matrix[skinningData.BindPose.Count];
+            worldTransforms = new Matrix[skinningData.BindPose.Count];
+            skinTransforms = new Matrix[skinningData.BindPose.Count];
+        }
+
+
+        /// <summary>
+        /// Starts decoding the specified animation clip.
+        /// </summary>
+        public void StartClip(AnimationClip clip)
+        {
+            if (clip == null)
+                throw new ArgumentNullException("clip");
+
+            currentClipValue = clip;
+            currentTimeValue = TimeSpan.Zero;
+            currentKeyframe = 0;
+
+            // Initialize bone transforms to the bind pose.
+            skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
+        }
+
+
+        /// <summary>
+        /// Advances the current animation position.
+        /// </summary>
+        public void Update(TimeSpan time, bool relativeToCurrentTime,
+                           Matrix rootTransform)
+        {
+            UpdateBoneTransforms(time, relativeToCurrentTime);
+            UpdateWorldTransforms(rootTransform);
+            UpdateSkinTransforms();
+        }
+
+
+        /// <summary>
+        /// Helper used by the Update method to refresh the BoneTransforms data.
+        /// </summary>
+        public void UpdateBoneTransforms(TimeSpan time, bool relativeToCurrentTime)
+        {
+            if (currentClipValue == null)
+                throw new InvalidOperationException(
+                            "AnimationPlayer.Update was called before StartClip");
+
+            // Update the animation position.
+            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))
+                throw new ArgumentOutOfRangeException("time");
+
+            // If the position moved backwards, reset the keyframe index.
+            if (time < currentTimeValue)
+            {
+                currentKeyframe = 0;
+                skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
+            }
+
+            currentTimeValue = time;
+
+            // Read keyframe matrices.
+            IList<Keyframe> keyframes = currentClipValue.Keyframes;
+
+            while (currentKeyframe < keyframes.Count)
+            {
+                Keyframe keyframe = keyframes[currentKeyframe];
+
+                // Stop when we've read up to the current time position.
+                if (keyframe.Time > currentTimeValue)
+                    break;
+
+                // Use this keyframe.
+                boneTransforms[keyframe.Bone] = keyframe.Transform;
+
+                currentKeyframe++;
+            }
+        }
+
+
+        /// <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; }
+        }
+    }
+}

BIN
SkinnedModel/Background.png


+ 62 - 0
SkinnedModel/Keyframe.cs

@@ -0,0 +1,62 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// Keyframe.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+#endregion
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// Describes the position of a single bone at a single point in time.
+    /// </summary>
+    public class Keyframe
+    {
+        /// <summary>
+        /// Constructs a new keyframe object.
+        /// </summary>
+        public Keyframe(int bone, TimeSpan time, Matrix transform)
+        {
+            Bone = bone;
+            Time = time;
+            Transform = transform;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private Keyframe()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets the index of the target bone that is animated by this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public int Bone { get; private set; }
+
+
+        /// <summary>
+        /// Gets the time offset from the start of the animation to this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public TimeSpan Time { get; private set; }
+
+
+        /// <summary>
+        /// Gets the bone transform for this keyframe.
+        /// </summary>
+        [ContentSerializer]
+        public Matrix Transform { get; private set; }
+    }
+}

+ 10 - 0
SkinnedModel/SkinnedModel.csproj

@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+</Project>

+ 76 - 0
SkinnedModel/SkinningData.cs

@@ -0,0 +1,76 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// SkinningData.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+#endregion
+
+namespace SkinnedModel
+{
+    /// <summary>
+    /// Combines all the data needed to render and animate a skinned object.
+    /// This is typically stored in the Tag property of the Model being animated.
+    /// </summary>
+    public class SkinningData
+    {
+        /// <summary>
+        /// Constructs a new skinning data object.
+        /// </summary>
+        public SkinningData(Dictionary<string, AnimationClip> animationClips,
+                            List<Matrix> bindPose, List<Matrix> inverseBindPose,
+                            List<int> skeletonHierarchy)
+        {
+            AnimationClips = animationClips;
+            BindPose = bindPose;
+            InverseBindPose = inverseBindPose;
+            SkeletonHierarchy = skeletonHierarchy;
+        }
+
+
+        /// <summary>
+        /// Private constructor for use by the XNB deserializer.
+        /// </summary>
+        private SkinningData()
+        {
+        }
+
+
+        /// <summary>
+        /// Gets a collection of animation clips. These are stored by name in a
+        /// dictionary, so there could for instance be clips for "Walk", "Run",
+        /// "JumpReallyHigh", etc.
+        /// </summary>
+        [ContentSerializer]
+        public Dictionary<string, AnimationClip> AnimationClips { get; private set; }
+
+
+        /// <summary>
+        /// Bindpose matrices for each bone in the skeleton,
+        /// relative to the parent bone.
+        /// </summary>
+        [ContentSerializer]
+        public List<Matrix> BindPose { get; private set; }
+
+
+        /// <summary>
+        /// Vertex to bonespace transforms for each bone in the skeleton.
+        /// </summary>
+        [ContentSerializer]
+        public List<Matrix> InverseBindPose { get; private set; }
+
+
+        /// <summary>
+        /// For each bone in the skeleton, stores the index of the parent bone.
+        /// </summary>
+        [ContentSerializer]
+        public List<int> SkeletonHierarchy { get; private set; }
+    }
+}

+ 16 - 0
SkinnedModelPipeline/SkinnedModelPipeline.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.Content.Pipeline" Version="3.8.0.1641">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
+      <PrivateAssets>All</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SkinnedModel\SkinnedModel.csproj" />
+  </ItemGroup>
+</Project>

+ 271 - 0
SkinnedModelPipeline/SkinnedModelProcessor.cs

@@ -0,0 +1,271 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// SkinnedModelProcessor.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
+using Microsoft.Xna.Framework.Content.Pipeline.Processors;
+using SkinnedModel;
+#endregion
+
+namespace SkinnedModelPipeline
+{
+    /// <summary>
+    /// Custom processor extends the builtin framework ModelProcessor class,
+    /// adding animation support.
+    /// </summary>
+    [ContentProcessor]
+    public class SkinnedModelProcessor : ModelProcessor
+    {
+        /// <summary>
+        /// The main Process method converts an intermediate format content pipeline
+        /// NodeContent tree to a ModelContent object with embedded animation data.
+        /// </summary>
+        public override ModelContent Process(NodeContent input,
+                                             ContentProcessorContext context)
+        {
+            ValidateMesh(input, context, null);
+
+            // Find the skeleton.
+            BoneContent skeleton = MeshHelper.FindSkeleton(input);
+
+            if (skeleton == null)
+                throw new InvalidContentException("Input skeleton not found.");
+
+            // We don't want to have to worry about different parts of the model being
+            // in different local coordinate systems, so let's just bake everything.
+            FlattenTransforms(input, skeleton);
+
+            // Read the bind pose and skeleton hierarchy data.
+            IList<BoneContent> bones = MeshHelper.FlattenSkeleton(skeleton);
+
+            if (bones.Count > SkinnedEffect.MaxBones + 100)
+            {
+                throw new InvalidContentException(string.Format(
+                    "Skeleton has {0} bones, but the maximum supported is {1}.",
+                    bones.Count, SkinnedEffect.MaxBones));
+            }
+
+            List<Matrix> bindPose = new List<Matrix>();
+            List<Matrix> inverseBindPose = new List<Matrix>();
+            List<int> skeletonHierarchy = new List<int>();
+
+            foreach (BoneContent bone in bones)
+            {
+                bindPose.Add(bone.Transform);
+                inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform));
+                skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent));
+            }
+
+            // Convert animation data to our runtime format.
+            Dictionary<string, AnimationClip> animationClips;
+            animationClips = ProcessAnimations(skeleton.Animations, bones);
+
+            // Chain to the base ModelProcessor class so it can convert the model data.
+            ModelContent model = base.Process(input, context);
+
+            // Store our custom animation data in the Tag property of the model.
+            model.Tag = new SkinningData(animationClips, bindPose,
+                                         inverseBindPose, skeletonHierarchy);
+
+            return model;
+        }
+
+
+        /// <summary>
+        /// Converts an intermediate format content pipeline AnimationContentDictionary
+        /// object to our runtime AnimationClip format.
+        /// </summary>
+        static Dictionary<string, AnimationClip> ProcessAnimations(
+            AnimationContentDictionary animations, IList<BoneContent> bones)
+        {
+            // Build up a table mapping bone names to indices.
+            Dictionary<string, int> boneMap = new Dictionary<string, int>();
+
+            for (int i = 0; i < bones.Count; i++)
+            {
+                string boneName = bones[i].Name;
+
+                if (!string.IsNullOrEmpty(boneName))
+                    boneMap.Add(boneName, i);
+            }
+
+            // Convert each animation in turn.
+            Dictionary<string, AnimationClip> animationClips;
+            animationClips = new Dictionary<string, AnimationClip>();
+
+            foreach (KeyValuePair<string, AnimationContent> animation in animations)
+            {
+                AnimationClip processed = ProcessAnimation(animation.Value, boneMap);
+                
+                animationClips.Add(animation.Key, processed);
+            }
+
+            if (animationClips.Count == 0)
+            {
+                throw new InvalidContentException(
+                            "Input file does not contain any animations.");
+            }
+
+            return animationClips;
+        }
+
+
+        /// <summary>
+        /// Converts an intermediate format content pipeline AnimationContent
+        /// object to our runtime AnimationClip format.
+        /// </summary>
+        static AnimationClip ProcessAnimation(AnimationContent animation,
+                                              Dictionary<string, int> boneMap)
+        {
+            List<Keyframe> keyframes = new List<Keyframe>();
+
+            // For each input animation channel.
+            foreach (KeyValuePair<string, AnimationChannel> channel in
+                animation.Channels)
+            {
+                // Look up what bone this channel is controlling.
+                int boneIndex;
+
+                if (!boneMap.TryGetValue(channel.Key, out boneIndex))
+                {
+                    throw new InvalidContentException(string.Format(
+                        "Found animation for bone '{0}', " +
+                        "which is not part of the skeleton.", channel.Key));
+                }
+
+                // Convert the keyframe data.
+                foreach (AnimationKeyframe keyframe in channel.Value)
+                {
+                    keyframes.Add(new Keyframe(boneIndex, keyframe.Time,
+                                               keyframe.Transform));
+                }
+            }
+
+            // Sort the merged keyframes by time.
+            keyframes.Sort(CompareKeyframeTimes);
+
+            if (keyframes.Count == 0)
+                throw new InvalidContentException("Animation has no keyframes.");
+
+            if (animation.Duration <= TimeSpan.Zero)
+                throw new InvalidContentException("Animation has a zero duration.");
+
+            return new AnimationClip(animation.Duration, keyframes);
+        }
+
+
+        /// <summary>
+        /// Comparison function for sorting keyframes into ascending time order.
+        /// </summary>
+        static int CompareKeyframeTimes(Keyframe a, Keyframe b)
+        {
+            return a.Time.CompareTo(b.Time);
+        }
+
+
+        /// <summary>
+        /// Makes sure this mesh contains the kind of data we know how to animate.
+        /// </summary>
+        static void ValidateMesh(NodeContent node, ContentProcessorContext context,
+                                 string parentBoneName)
+        {
+            MeshContent mesh = node as MeshContent;
+
+            if (mesh != null)
+            {
+                // Validate the mesh.
+                if (parentBoneName != null)
+                {
+                    context.Logger.LogWarning(null, null,
+                        "Mesh {0} is a child of bone {1}. SkinnedModelProcessor " +
+                        "does not correctly handle meshes that are children of bones.",
+                        mesh.Name, parentBoneName);
+                }
+
+                if (!MeshHasSkinning(mesh))
+                {
+                    context.Logger.LogWarning(null, null,
+                        "Mesh {0} has no skinning information, so it has been deleted.",
+                        mesh.Name);
+
+                    mesh.Parent.Children.Remove(mesh);
+                    return;
+                }
+            }
+            else if (node is BoneContent)
+            {
+                // If this is a bone, remember that we are now looking inside it.
+                parentBoneName = node.Name;
+            }
+
+            // Recurse (iterating over a copy of the child collection,
+            // because validating children may delete some of them).
+            foreach (NodeContent child in new List<NodeContent>(node.Children))
+                ValidateMesh(child, context, parentBoneName);
+        }
+
+
+        /// <summary>
+        /// Checks whether a mesh contains skininng information.
+        /// </summary>
+        static bool MeshHasSkinning(MeshContent mesh)
+        {
+            foreach (GeometryContent geometry in mesh.Geometry)
+            {
+                if (!geometry.Vertices.Channels.Contains(VertexChannelNames.Weights()))
+                    return false;
+            }
+
+            return true;
+        }
+
+
+        /// <summary>
+        /// Bakes unwanted transforms into the model geometry,
+        /// so everything ends up in the same coordinate system.
+        /// </summary>
+        static void FlattenTransforms(NodeContent node, BoneContent skeleton)
+        {
+            foreach (NodeContent child in node.Children)
+            {
+                // Don't process the skeleton, because that is special.
+                if (child == skeleton)
+                    continue;
+
+                // Bake the local transform into the actual geometry.
+                MeshHelper.TransformScene(child, child.Transform);
+
+                // Having baked it, we can now set the local
+                // coordinate system back to identity.
+                child.Transform = Matrix.Identity;
+
+                // Recurse.
+                FlattenTransforms(child, skeleton);
+            }
+        }
+
+
+        /// <summary>
+        /// Force all the materials to use our skinned model effect.
+        /// </summary>
+        [DefaultValue(MaterialProcessorDefaultEffect.SkinnedEffect)]
+        public override MaterialProcessorDefaultEffect DefaultEffect
+        {
+            get { return MaterialProcessorDefaultEffect.SkinnedEffect; }
+            set { }
+        }
+    }
+}

+ 65 - 0
SkinnedModel_MonoGame.sln

@@ -0,0 +1,65 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30611.23
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkinningSample", "SkinningSample\SkinningSample.csproj", "{AA4F2198-9249-457C-BD92-2EF238280A75}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkinnedModel", "SkinnedModel\SkinnedModel.csproj", "{D9727887-08F4-436C-B873-1E757E94621F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkinnedModelPipeline", "SkinnedModelPipeline\SkinnedModelPipeline.csproj", "{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|x64.Build.0 = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Debug|x86.Build.0 = Debug|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|x64.ActiveCfg = Release|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|x64.Build.0 = Release|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|x86.ActiveCfg = Release|Any CPU
+		{AA4F2198-9249-457C-BD92-2EF238280A75}.Release|x86.Build.0 = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|x64.Build.0 = Debug|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Debug|x86.Build.0 = Debug|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|x64.ActiveCfg = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|x64.Build.0 = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|x86.ActiveCfg = Release|Any CPU
+		{D9727887-08F4-436C-B873-1E757E94621F}.Release|x86.Build.0 = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|x64.Build.0 = Debug|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Debug|x86.Build.0 = Debug|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|x64.ActiveCfg = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|x64.Build.0 = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|x86.ActiveCfg = Release|Any CPU
+		{B7C1621F-40BF-4649-B4D5-2A898E92B3FB}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {F1529F9C-A5B1-4100-8796-E289D8A6CD65}
+	EndGlobalSection
+EndGlobal

BIN
SkinningSample/Background.png


+ 35 - 0
SkinningSample/Content/Content.mgcb

@@ -0,0 +1,35 @@
+
+#----------------------------- Global Properties ----------------------------#
+
+/outputDir:bin/$(Platform)
+/intermediateDir:obj/$(Platform)
+/platform:DesktopGL
+/config:
+/profile:Reach
+/compress:False
+
+#-------------------------------- References --------------------------------#
+
+/reference:..\..\SkinnedModelPipeline\bin\Release\netstandard2.0\SkinnedModelPipeline.dll
+
+#---------------------------------- Content ---------------------------------#
+
+#begin dude.fbx
+/importer:FbxImporter
+/processor:SkinnedModelProcessor
+/processorParam:DefaultEffect=SkinnedEffect
+/processorParam:ColorKeyColor=0,0,0,0
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=True
+/processorParam:GenerateTangentFrames=False
+/processorParam:PremultiplyTextureAlpha=True
+/processorParam:PremultiplyVertexColors=True
+/processorParam:ResizeTexturesToPowerOfTwo=False
+/processorParam:RotationX=0
+/processorParam:RotationY=0
+/processorParam:RotationZ=0
+/processorParam:Scale=1
+/processorParam:SwapWindingOrder=False
+/processorParam:TextureFormat=Compressed
+/build:dude.fbx
+

BIN
SkinningSample/Content/dude.fbx


BIN
SkinningSample/Content/head.tga


BIN
SkinningSample/Content/jacket.tga


BIN
SkinningSample/Content/pants.tga


BIN
SkinningSample/Content/upBodyC.tga


BIN
SkinningSample/Game.ico


BIN
SkinningSample/Icon.bmp


BIN
SkinningSample/Icon.ico


+ 14 - 0
SkinningSample/Program.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace SkinningSample
+{
+    public static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            using (var game = new SkinningSampleGame())
+                game.Run();
+        }
+    }
+}

+ 242 - 0
SkinningSample/SkinningSample.cs

@@ -0,0 +1,242 @@
+#region File Description
+//-----------------------------------------------------------------------------
+// SkinningSample.cs
+//
+// Microsoft XNA Community Game Platform
+// Copyright (C) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#endregion
+
+#region Using Statements
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using SkinnedModel;
+#endregion
+
+namespace SkinningSample
+{
+    /// <summary>
+    /// Sample game showing how to display skinned character animation.
+    /// </summary>
+    public class SkinningSampleGame : Microsoft.Xna.Framework.Game
+    {
+        #region Fields
+
+        GraphicsDeviceManager graphics;
+
+        KeyboardState currentKeyboardState = new KeyboardState();
+        GamePadState currentGamePadState = new GamePadState();
+
+        Model currentModel;
+        AnimationPlayer animationPlayer;
+
+        float cameraArc = 0;
+        float cameraRotation = 0;
+        float cameraDistance = 100;
+
+        #endregion
+
+        #region Initialization
+
+
+        public SkinningSampleGame()
+        {
+            graphics = new GraphicsDeviceManager(this);
+            Content.RootDirectory = "Content";
+
+#if WINDOWS_PHONE
+            // Frame rate is 30 fps by default for Windows Phone.
+            TargetElapsedTime = TimeSpan.FromTicks(333333);
+            
+            graphics.IsFullScreen = true;            
+#endif
+        }
+
+
+        /// <summary>
+        /// Load your graphics content.
+        /// </summary>
+        protected override void LoadContent()
+        {
+            // Load the model.
+            currentModel = Content.Load<Model>("dude");
+
+            // Look up our custom skinning information.
+            SkinningData skinningData = currentModel.Tag as SkinningData;
+
+            if (skinningData == null)
+                throw new InvalidOperationException
+                    ("This model does not contain a SkinningData tag.");
+
+            // Create an animation player, and start decoding an animation clip.
+            animationPlayer = new AnimationPlayer(skinningData);
+
+            AnimationClip clip = skinningData.AnimationClips["Take 001"];
+
+            animationPlayer.StartClip(clip);
+        }
+
+
+        #endregion
+
+        #region Update and Draw
+
+
+        /// <summary>
+        /// Allows the game to run logic.
+        /// </summary>
+        protected override void Update(GameTime gameTime)
+        {
+            HandleInput();
+
+            UpdateCamera(gameTime);
+            
+            animationPlayer.Update(gameTime.ElapsedGameTime, true, Matrix.Identity);
+
+            base.Update(gameTime);
+        }
+
+
+        /// <summary>
+        /// This is called when the game should draw itself.
+        /// </summary>
+        protected override void Draw(GameTime gameTime)
+        {
+            GraphicsDevice device = graphics.GraphicsDevice;
+
+            device.Clear(Color.CornflowerBlue);
+
+            Matrix[] bones = animationPlayer.GetSkinTransforms();
+
+            // Compute camera matrices.
+            Matrix view = Matrix.CreateTranslation(0, -40, 0) * 
+                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
+                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
+                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance), 
+                                              new Vector3(0, 0, 0), Vector3.Up);
+
+            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
+                                                                    device.Viewport.AspectRatio,
+                                                                    1,
+                                                                    10000);
+
+            // Render the skinned mesh.
+            foreach (ModelMesh mesh in currentModel.Meshes)
+            {
+                foreach (SkinnedEffect effect in mesh.Effects)
+                {
+                    effect.SetBoneTransforms(bones);
+
+                    effect.View = view;
+                    effect.Projection = projection;
+
+                    effect.EnableDefaultLighting();
+
+                    effect.SpecularColor = new Vector3(0.25f);
+                    effect.SpecularPower = 16;
+                }
+
+                mesh.Draw();
+            }
+
+            base.Draw(gameTime);
+        }
+
+        
+        #endregion
+
+        #region Handle Input
+
+
+        /// <summary>
+        /// Handles input for quitting the game.
+        /// </summary>
+        private void HandleInput()
+        {
+            currentKeyboardState = Keyboard.GetState();
+            currentGamePadState = GamePad.GetState(PlayerIndex.One);
+
+            // Check for exit.
+            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
+                currentGamePadState.Buttons.Back == ButtonState.Pressed)
+            {
+                Exit();
+            }
+        }
+
+
+        /// <summary>
+        /// Handles camera input.
+        /// </summary>
+        private void UpdateCamera(GameTime gameTime)
+        {
+            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
+
+            // Check for input to rotate the camera up and down around the model.
+            if (currentKeyboardState.IsKeyDown(Keys.Up) ||
+                currentKeyboardState.IsKeyDown(Keys.W))
+            {
+                cameraArc += time * 0.1f;
+            }
+            
+            if (currentKeyboardState.IsKeyDown(Keys.Down) ||
+                currentKeyboardState.IsKeyDown(Keys.S))
+            {
+                cameraArc -= time * 0.1f;
+            }
+
+            cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.25f;
+
+            // Limit the arc movement.
+            if (cameraArc > 90.0f)
+                cameraArc = 90.0f;
+            else if (cameraArc < -90.0f)
+                cameraArc = -90.0f;
+
+            // Check for input to rotate the camera around the model.
+            if (currentKeyboardState.IsKeyDown(Keys.Right) ||
+                currentKeyboardState.IsKeyDown(Keys.D))
+            {
+                cameraRotation += time * 0.1f;
+            }
+
+            if (currentKeyboardState.IsKeyDown(Keys.Left) ||
+                currentKeyboardState.IsKeyDown(Keys.A))
+            {
+                cameraRotation -= time * 0.1f;
+            }
+
+            cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.25f;
+
+            // Check for input to zoom camera in and out.
+            if (currentKeyboardState.IsKeyDown(Keys.Z))
+                cameraDistance += time * 0.25f;
+
+            if (currentKeyboardState.IsKeyDown(Keys.X))
+                cameraDistance -= time * 0.25f;
+
+            cameraDistance += currentGamePadState.Triggers.Left * time * 0.5f;
+            cameraDistance -= currentGamePadState.Triggers.Right * time * 0.5f;
+
+            // Limit the camera distance.
+            if (cameraDistance > 500.0f)
+                cameraDistance = 500.0f;
+            else if (cameraDistance < 10.0f)
+                cameraDistance = 10.0f;
+
+            if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
+                currentKeyboardState.IsKeyDown(Keys.R))
+            {
+                cameraArc = 0;
+                cameraRotation = 0;
+                cameraDistance = 100;
+            }
+        }
+
+
+        #endregion
+    }
+}

+ 33 - 0
SkinningSample/SkinningSample.csproj

@@ -0,0 +1,33 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <PublishReadyToRun>false</PublishReadyToRun>
+    <TieredCompilation>false</TieredCompilation>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <ApplicationIcon>Icon.ico</ApplicationIcon>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Icon.ico" />
+    <None Remove="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Icon.ico" />
+    <EmbeddedResource Include="Icon.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <MonoGameContentReference Include="Content\Content.mgcb" />
+  </ItemGroup>
+  <ItemGroup>
+    <TrimmerRootAssembly Include="Microsoft.Xna.Framework.Content.ContentTypeReader" Visible="false" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641" />
+    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.0.1641" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\SkinnedModel\SkinnedModel.csproj" />
+  </ItemGroup>
+</Project>

BIN
SkinningSample/SkinningSample.png


+ 43 - 0
SkinningSample/app.manifest

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="SkinningSample"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on and is
+           is designed to work with. Uncomment the appropriate elements and Windows will 
+           automatically selected the most compatible environment. -->
+
+      <!-- Windows Vista -->
+      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+    </application>
+  </compatibility>
+
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+    </windowsSettings>
+  </application>
+
+</assembly>