/// <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> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static Dictionary<string, AnimationClip> ProcessAnimations( AnimationContentDictionary animations, IList<BoneContent> bones, ContentProcessorContext context, ContentIdentity sourceIdentity) { // 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>(); // We process the original animation first, so we can use their keyframes foreach (KeyValuePair<string, AnimationContent> animation in animations) { AnimationClip processed = ProcessAnimation(animation.Value, boneMap, animation.Key); animationClips.Add(animation.Key, processed); } // Check to see if there's an animation clip definition // Here, we're checking for a file with the _Anims suffix. // So, if your model is named dude.fbx, we'd add dude_Anims.xml in the same folder // and the pipeline will see the file and use it to override the animations in the // original model file. string SourceModelFile = sourceIdentity.SourceFilename; string SourcePath = Path.GetDirectoryName(SourceModelFile); string AnimFilename = Path.GetFileNameWithoutExtension(SourceModelFile); AnimFilename += "_Anims.xml"; string AnimPath = Path.Combine(SourcePath, AnimFilename); if (File.Exists(AnimPath)) { // Add the filename as a dependency, so if it changes, the model is rebuilt context.AddDependency(AnimPath); // Load the animation definition from the XML file AnimationDefinition AnimDef = context.BuildAndLoadAsset<XmlImporter, AnimationDefinition>(new ExternalReference<XmlImporter>(AnimPath), null); // Break up the original animation clips into our new clips // First, we check if the clips contains our clip to break up if (animationClips.ContainsKey(AnimDef.OriginalClipName)) { // Grab the main clip that we are using AnimationClip MainClip = animationClips[AnimDef.OriginalClipName]; // Now remove the original clip from our animations animationClips.Remove(AnimDef.OriginalClipName); // Process each of our new animation clip parts foreach (AnimationDefinition.ClipPart Part in AnimDef.ClipParts) { // Calculate the frame times TimeSpan StartTime = GetTimeSpanForFrame(Part.StartFrame, AnimDef.OriginalFrameCount, MainClip.Duration.Ticks); TimeSpan EndTime = GetTimeSpanForFrame(Part.EndFrame, AnimDef.OriginalFrameCount, MainClip.Duration.Ticks); // Get all the keyframes for the animation clip // that fall within the start and end time List<Keyframe> Keyframes = new List<Keyframe>(); foreach (Keyframe AnimFrame in MainClip.Keyframes) { if ((AnimFrame.Time >= StartTime) && (AnimFrame.Time <= EndTime)) { Keyframe NewFrame = new Keyframe(AnimFrame.Bone, AnimFrame.Time - StartTime, AnimFrame.Transform); Keyframes.Add(NewFrame); } } // Process the events List<AnimationEvent> Events = new List<AnimationEvent>(); if (Part.Events != null) { // Process each event foreach (AnimationDefinition.ClipPart.Event Event in Part.Events) { // Get the event time within the animation TimeSpan EventTime = GetTimeSpanForFrame(Event.Keyframe, AnimDef.OriginalFrameCount, MainClip.Duration.Ticks); // Offset the event time so it is relative to the start of the animation EventTime -= StartTime; // Create the event AnimationEvent NewEvent = new AnimationEvent(); NewEvent.EventTime = EventTime; NewEvent.EventName = Event.Name; Events.Add(NewEvent); } } // Create the clip AnimationClip NewClip = new AnimationClip(EndTime - StartTime, Keyframes, Events, Part.ClipName); animationClips[Part.ClipName] = NewClip; } } } if (animationClips.Count == 0) { throw new InvalidContentException( "Input file does not contain any animations."); } return animationClips; }