} // ProcessVertexChannel #endregion #region Process Animations /// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary object to our runtime AnimationClip format. /// </summary> static void ProcessAnimations(NodeContent input, ModelContent model, Dictionary<string, ModelAnimationClip> modelAnimationClips, Dictionary<string, RootAnimationClip> rootAnimationClips) { // Build up a table mapping bone names to indices. Dictionary<string, int> boneMap = new Dictionary<string, int>(); for (int i = 0; i < model.Bones.Count; i++) { string boneName = model.Bones[i].Name; if (!string.IsNullOrEmpty(boneName)) boneMap.Add(boneName, i); } // Convert each animation in the root of the object foreach (KeyValuePair<string, AnimationContent> animation in input.Animations) { RootAnimationClip processed = ProcessRootAnimation(animation.Value, model.Bones[0].Name); rootAnimationClips.Add(animation.Key, processed); } // Get the unique names of the animations on the mesh children List<string> animationNames = new List<string>(); AddAnimationNodes(animationNames, input); // Now create those animations foreach (string key in animationNames) { ModelAnimationClip processed = ProcessRigidAnimation(key, boneMap, input, model); modelAnimationClips.Add(key, processed); } } // ProcessAnimations
/// <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 > ModelAnimationClip.MaxBones) { throw new InvalidContentException(string.Format("Skeleton has {0} bones, but the maximum supported is {1}.", bones.Count, ModelAnimationClip.MaxBones)); } List <Matrix> bindPose = new List <Matrix>(); List <Matrix> inverseBindPose = new List <Matrix>(); List <int> skeletonHierarchy = new List <int>(); Dictionary <string, int> boneIndices = new Dictionary <string, int>(); foreach (BoneContent bone in bones) { bindPose.Add(bone.Transform); inverseBindPose.Add(Matrix.Invert(bone.AbsoluteTransform)); skeletonHierarchy.Add(bones.IndexOf(bone.Parent as BoneContent)); boneIndices.Add(bone.Name, boneIndices.Count); } // Convert animation data to our runtime format. Dictionary <string, ModelAnimationClip> modelAnimationClips = ProcessAnimations(skeleton.Animations, bones, context); Dictionary <string, RootAnimationClip> rootAnimationClips = new Dictionary <string, RootAnimationClip>(); // Chain to the base ModelProcessor class so it can convert the model data. ModelContent model = base.Process(input, context); // Convert each animation in the root of the object foreach (KeyValuePair <string, AnimationContent> animation in input.Animations) { RootAnimationClip processed = RigidModelProcessor.ProcessRootAnimation(animation.Value, model.Bones[0].Name); rootAnimationClips.Add(animation.Key, processed); } // Store our custom animation data in the Tag property of the model. model.Tag = new ModelAnimationData(modelAnimationClips, rootAnimationClips, bindPose, inverseBindPose, skeletonHierarchy, boneIndices); return(model); } // Process
} // ResumeClip #endregion #region Stop /// <summary> /// Stops playing the sound. /// </summary> /// <param name="immediate"> /// Specifies whether to stop playing immediately, or to break out of the loop region and play the release. /// Specify true to stop playing immediately, or false to break out of the loop region and play the release phase (the remainder of the sound). /// </param> public void Stop(bool immediate = true) { if (immediate) { currentAnimationClip = null; } else { stopWhenCicleFinishes = true; } } // Stop
} // Initialize #endregion #region Uninitialize /// <summary> /// Uninitialize the component. /// Is important to remove event associations and any other reference. /// </summary> internal override void Uninitialize() { currentAnimationClip = null; cachedModel = null; ((GameObject3D)Owner).ModelFilterChanged -= OnModelFilterChanged; if (((GameObject3D)Owner).ModelFilter != null) { ((GameObject3D)Owner).ModelFilter.ModelChanged -= OnModelChanged; } // Call this last because the owner information is needed. base.Uninitialize(); } // Uninitialize
} // Stop #endregion #region Update /// <summary> /// Called during the update loop to move the animation forward /// </summary> public virtual void Update() { if (currentAnimationClip == null) { return; } if (paused) { return; } // Adjust for the rate float time = Time.GameDeltaTime * playbackRate; elapsedPlaybackTime += time; // See if we should terminate if (elapsedPlaybackTime > duration && duration != 0 || elapsedPlaybackTime > currentAnimationClip.Duration && duration == 0) { // Animation Completed currentAnimationClip = null; return; } // Update the animation position. time += currentTimeValue; // If we reached the end, loop back to the start. while (time >= currentAnimationClip.Duration) { if (stopWhenCicleFinishes) { currentAnimationClip = null; return; } time -= currentAnimationClip.Duration; } CurrentTimeValue = time; // Update transform Matrix. // The animation information is absolute but we need relative information so that different transformations could be applied to the transform at the same time. ((GameObject3D)Owner).Transform.LocalMatrix = currentKeyFrame.Transform * Matrix.Invert(previousAnimationTransform) * ((GameObject3D)Owner).Transform.LocalMatrix; previousAnimationTransform = currentKeyFrame.Transform; } // Update
} // RootAnimation /// <summary> /// Load root animation data from a .x or fbx file. /// </summary> public RootAnimation(string filename) { Name = filename; Filename = AssetContentManager.GameDataDirectory + "Animations\\" + filename; if (File.Exists(Filename + ".xnb") == false) { throw new ArgumentException("Failed to load animation data: File " + Filename + " does not exists!", "filename"); } try { Resource = AssetContentManager.CurrentContentManager.XnaContentManager.Load<RootAnimationClip>(Filename); } catch (ObjectDisposedException) { throw new InvalidOperationException("Content Manager: Content manager disposed"); } catch (Exception e) { throw new InvalidOperationException("Failed to load animation data: " + filename, e); } } // RootAnimation
} // Play /// <summary> /// Starts playing a clip /// </summary> /// <param name="name">Animation name</param> /// <param name="playbackRate">Speed to playback</param> /// <param name="duration">Length of time to play in seconds (max is looping, 0 is once)</param> public void Play(string name, float playbackRate, float duration) { if (!rootAnimations.ContainsKey(name)) { throw new ArgumentException("Root Animation Component: the animation name does not exist."); } // Store the clip and reset playing data currentAnimationClip = rootAnimations[name]; currentKeyFrameIndex = 0; currentKeyFrame = currentAnimationClip.Keyframes[0]; CurrentTimeValue = 0; elapsedPlaybackTime = 0; paused = false; // Store the data about how we want to playback this.playbackRate = playbackRate; this.duration = duration; previousAnimationTransform = ((GameObject3D)Owner).Transform.LocalMatrix; } // Play
} // RootAnimation #endregion #region Recreate Resource /// <summary> /// Useful when the XNA device is disposed. /// </summary> internal override void RecreateResource() { Resource = AssetContentManager.CurrentContentManager.XnaContentManager.Load<RootAnimationClip>(Filename); } // RecreateResource
/// <summary> /// Internal Constructor for File Model assets. /// </summary> internal RootAnimation(string name, RootAnimationClip resource) { Name = name; Resource = resource; ContentManager = null; // This is controled by the own file model. } // RootAnimation