private bool ValidateModel(NodeContent input, BoneContent rootBone, ContentProcessorContext context) { // Finds the root bone if (rootBone == null) { throw new InvalidContentException("Input model does not contain a skeleton."); } // Validate maximum supported bones IList <BoneContent> boneList = MeshHelper.FlattenSkeleton(rootBone); if (boneList.Count > MaxBones) { throw new InvalidContentException(string.Format( "Model's skeleton has {0} bones, but the maximum supported is {1}.", boneList.Count, MaxBones)); } // Find animations AnimationContentDictionary animationDictionary = rootBone.Animations; if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, rootBone.Identity, "Input model does not contain any animation."); } return(true); }
/// <summary> /// This function processes the animation content of the model, and stores the data in the Matrix[] List /// </summary> /// <param name="animations"></param> /// <param name="bones"></param> /// <param name="outKeyFrames"></param> /// <returns></returns> private Dictionary <string, InstancedAnimationClip> ProcessAnimations( AnimationContentDictionary animations, IList <BoneContent> bones, List <Matrix[]> outKeyFrames) { // Create a map of bones - just like in the SkinnedModel Smaple 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); } } // This is a dictionary of all the animations in the model Dictionary <string, InstancedAnimationClip> animationDictionary = new Dictionary <string, InstancedAnimationClip>(); // Loop through each animation, and add that stuff to our animation texture, as well as to our dictionary we are going to output foreach (KeyValuePair <string, AnimationContent> data in animations) { string animationName = data.Key; AnimationContent animation = data.Value; InstancedAnimationClip clip = ProcessAnimation(animation, outKeyFrames, boneMap); animationDictionary.Add(animationName, clip); } return(animationDictionary); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static Dictionary <string, Clip> ProcessAnimations( AnimationContentDictionary animations, IList <BoneContent> bones, List <ClipData> clipInfo) { // 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, Clip> animationClips = new Dictionary <string, Clip>(); foreach (ClipData clip in clipInfo) { Clip processed = ProcessAnimation(animations[clip.SourceTake], boneMap, clip); animationClips.Add(clip.Alias, processed); } if (animationClips.Count == 0) { throw new InvalidContentException("Input file does not contain any animations."); } return(animationClips); }
/// <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; }
protected virtual AnimationContentDictionary MergeAnimatedBones(NodeContent root) { AnimationContentDictionary ret = new AnimationContentDictionary(); CollectAnimatedBones(ret, root); return(ret); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> /// <param name="animations">Animation content dictionary.</param> /// <param name="bones">List of bones.</param> /// <returns>Dictionary of animation clips ordered by name.</returns> 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 = 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 AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static Dictionary <string, Clip> 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, Clip> animationClips; animationClips = new Dictionary <string, Clip>(); foreach (KeyValuePair <string, AnimationContent> animation in animations) { Clip processed = ProcessAnimation(animation.Value, boneMap); processed.Name = animation.Key; animationClips.Add(animation.Key, processed); } return(animationClips); }
/// <summary> /// NodeContentに含まれているアニメーションデータから独自形式のアニメーションデータを構築 /// </summary> static Dictionary <string, AnimationClip> ProcessAnimations(AnimationContentDictionary animations, IList <BoneContent> bones) { // ボーン名をキーにしたボーンインデックス配列を作成 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); } } // クリップ名をキーにしたアニメショーン配列を作成 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); }
private static void MergeAnimation(string animationFilePath, AnimationContentDictionary animationDictionary, ContentIdentity contentIdentity, ContentProcessorContext context) { // Use content pipeline to build the asset. NodeContent mergeModel = context.BuildAndLoadAsset<NodeContent, NodeContent>(new ExternalReference<NodeContent>(animationFilePath), null); // Find the skeleton. BoneContent mergeRoot = MeshHelper.FindSkeleton(mergeModel); if (mergeRoot == null) { context.Logger.LogWarning(null, contentIdentity, "Animation model file '{0}' has no root bone. Cannot merge animations.", animationFilePath); return; } // Merge all animations of the skeleton root node. foreach (string animationName in mergeRoot.Animations.Keys) { if (animationDictionary.ContainsKey(animationName)) { context.Logger.LogWarning(null, contentIdentity, "Replacing animation '{0}' from '{1}' with merged animation.", animationName, animationFilePath); animationDictionary[animationName] = mergeRoot.Animations[animationName]; } else { context.Logger.LogImportantMessage("Merging animation '{0}' from '{1}'.", animationName, animationFilePath); animationDictionary.Add(animationName, mergeRoot.Animations[animationName]); } } }
static void ProcessAnimations(AnimationContentDictionary xnaAnimations, SerializableModel model) { foreach (KeyValuePair <string, AnimationContent> xnaAnimation in xnaAnimations) { SerializableAnimation animation = ProcessAnimation(xnaAnimation.Value); model.animationList.Add(animation); } }
private static Dictionary <string, AnimationClip> ProcessAnimations(AnimationContentDictionary animations, IList <BoneContent> bones) { bool count; Dictionary <string, int> strs = new Dictionary <string, int>(); int num = 0; while (true) { count = num < bones.Count; if (!count) { break; } string name = bones[num].Name; count = string.IsNullOrEmpty(name); if (!count) { strs.Add(name, num); } num++; } Dictionary <string, AnimationClip> strs1 = new Dictionary <string, AnimationClip>(); IEnumerator <KeyValuePair <string, AnimationContent> > enumerator = animations.GetEnumerator(); try { while (true) { count = enumerator.MoveNext(); if (!count) { break; } KeyValuePair <string, AnimationContent> current = enumerator.Current; AnimationClip animationClip = SkinnedModelProcessor.ProcessAnimation(current.Value, strs); strs1.Add(current.Key, animationClip); } } finally { count = enumerator == null; if (!count) { enumerator.Dispose(); } } count = strs1.Count != 0; if (count) { Dictionary <string, AnimationClip> strs2 = strs1; return(strs2); } else { throw new InvalidContentException("Input file does not contain any animations."); } }
public static void Split(AnimationContentDictionary animationDictionary, string splitFile, ContentIdentity contentIdentity, ContentProcessorContext context) { if (animationDictionary == null) return; if (string.IsNullOrEmpty(splitFile)) return; if (contentIdentity == null) throw new ArgumentNullException("contentIdentity"); if (context == null) throw new ArgumentNullException("context"); if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); // Load XML file. splitFile = ContentHelper.FindFile(splitFile, contentIdentity); XDocument document = XDocument.Load(splitFile, LoadOptions.SetLineInfo); // Let the content pipeline know that we depend on this file and we need to // rebuild the content if the file is modified. context.AddDependency(splitFile); // Parse XML. var animationsElement = document.Element("Animations"); if (animationsElement == null) { context.Logger.LogWarning(null, contentIdentity, "The animation split file \"{0}\" does not contain an <Animations> root node.", splitFile); return; } var wrappedContext = new ContentPipelineContext(context); var splits = ParseAnimationSplitDefinitions(animationsElement, contentIdentity, wrappedContext); if (splits == null || splits.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The XML file with the animation split definitions is invalid or empty. Animation is not split."); return; } // Split animations. Split(animationDictionary, splits, contentIdentity, context); }
/// <summary> /// Merges the specified animation files to the specified animation dictionary. /// </summary> /// <param name="animationFiles"> /// The animation files as a string separated by semicolon (relative to the folder of the model /// file). For example: "run.fbx;jump.fbx;turn.fbx". /// </param> /// <param name="animationDictionary">The animation dictionary.</param> /// <param name="contentIdentity">The content identity.</param> /// <param name="context">The content processor context.</param> public static void Merge(string animationFiles, AnimationContentDictionary animationDictionary, ContentIdentity contentIdentity, ContentProcessorContext context) { if (string.IsNullOrEmpty(animationFiles)) return; // Get path of the model file. var files = animationFiles.Split(';', ',') .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)); foreach (string file in files) { MergeAnimation(file, animationDictionary, contentIdentity, context); } }
Dictionary <string, AnimationClip> ProcessSplitAnimations( AnimationContentDictionary animations, Dictionary <string, int> boneMap) { IEnumerator <KeyValuePair <string, AnimationContent> > aniIter = animations.GetEnumerator(); aniIter.MoveNext(); Dictionary <string, AnimationClip> processed = SplitAndCreateAnimation(ProcessAnimationKeyframe(aniIter.Current.Value, boneMap)); if (processed.Count == 0) { throw new InvalidContentException("Input file does not contain any animations."); } return(processed); }
private Dictionary <string, AnimationClip> ProcessAnimations(AnimationContentDictionary animations, IList <BoneContent> boneContents) { Dictionary <string, int> boneMap = new Dictionary <string, int>(); for (int i = 0; i < boneContents.Count; i++) { boneMap.Add(boneContents[i].Name, i); } Dictionary <string, AnimationClip> animationClips = new Dictionary <string, AnimationClip>(); foreach (KeyValuePair <string, AnimationContent> animation in animations) { AnimationClip processed = ProcessAnimation(animation.Value, boneMap); animationClips.Add(animation.Key, processed); } return(animationClips); }
/// <summary> /// Merges the specified animation files to the specified animation dictionary. /// </summary> /// <param name="animationFiles"> /// The animation files as a string separated by semicolon (relative to the folder of the model /// file). For example: "run.fbx;jump.fbx;turn.fbx". /// </param> /// <param name="animationDictionary">The animation dictionary.</param> /// <param name="contentIdentity">The content identity.</param> /// <param name="context">The content processor context.</param> public static void Merge(string animationFiles, AnimationContentDictionary animationDictionary, ContentIdentity contentIdentity, ContentProcessorContext context) { if (string.IsNullOrEmpty(animationFiles)) return; // Get path of the model file. string sourcePath = Path.GetDirectoryName(contentIdentity.SourceFilename); var files = animationFiles.Split(';') .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)); foreach (string file in files) { string filePath = Path.GetFullPath(Path.Combine(sourcePath, file)); MergeAnimation(filePath, animationDictionary, contentIdentity, context); } }
protected virtual void CollectAnimatedBones(AnimationContentDictionary dict, NodeContent bone) { AnimationContentDictionary acd = bone.Animations; if (acd != null) { foreach (string name in acd.Keys) { // merge each animation into the dictionary AnimationContent ac = acd[name]; AnimationContent xac; if (!dict.TryGetValue(name, out xac)) { // create it if we haven't already seen it, and there's something there if (ac.Channels.Count > 0) { xac = ac; dict.Add(name, xac); } } else { // merge the animation content foreach (KeyValuePair <string, AnimationChannel> kvp in ac.Channels) { AnimationChannel ov; if (xac.Channels.TryGetValue(kvp.Key, out ov)) { throw new System.ArgumentException( String.Format("The animation {0} has multiple channels named {1}.", name, kvp.Key)); } xac.Channels.Add(kvp.Key, kvp.Value); } xac.Duration = new TimeSpan((long) (Math.Max(xac.Duration.TotalSeconds, ac.Duration.TotalSeconds) * 1e7)); } } } foreach (NodeContent nc in bone.Children) { CollectAnimatedBones(dict, nc); } }
private static void MergeAnimation(string animationFile, AnimationContentDictionary animationDictionary, ContentIdentity contentIdentity, ContentProcessorContext context) { if (string.IsNullOrEmpty(animationFile)) { return; } if (animationDictionary == null) { throw new ArgumentNullException("animationDictionary"); } if (context == null) { throw new ArgumentNullException("context"); } // Use content pipeline to build the asset. animationFile = ContentHelper.FindFile(animationFile, contentIdentity); NodeContent mergeModel = context.BuildAndLoadAsset <NodeContent, NodeContent>(new ExternalReference <NodeContent>(animationFile), null); // Find the skeleton. BoneContent mergeRoot = MeshHelper.FindSkeleton(mergeModel); if (mergeRoot == null) { context.Logger.LogWarning(null, contentIdentity, "Animation model file '{0}' has no root bone. Cannot merge animations.", animationFile); return; } // Merge all animations of the skeleton root node. foreach (string animationName in mergeRoot.Animations.Keys) { if (animationDictionary.ContainsKey(animationName)) { context.Logger.LogWarning(null, contentIdentity, "Replacing animation '{0}' with merged animation from '{1}'.", animationName, animationFile); animationDictionary[animationName] = mergeRoot.Animations[animationName]; } else { context.Logger.LogImportantMessage("Merging animation '{0}' from '{1}'.", animationName, animationFile); animationDictionary.Add(animationName, mergeRoot.Animations[animationName]); } } }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> Dictionary <string, AnimationClip> ProcessAnimations( AnimationContentDictionary animations, Dictionary <string, int> boneMap) { // Convert each animation in turn. Dictionary <string, AnimationClip> animationClips = new Dictionary <string, AnimationClip>(); foreach (KeyValuePair <string, AnimationContent> animation in animations) { animationClips.Add(animation.Key, ProcessAnimation(animation.Value, boneMap)); } if (animationClips.Count == 0) { throw new InvalidContentException("Input file does not contain any animations."); } return(animationClips); }
private AnimationClip[] ExtractAnimations( AnimationContentDictionary animationDictionary, List <string> boneNameList, ContentProcessorContext context) { if (animationDictionary.Count < 1) { context.Logger.LogImportantMessage("Warning: No animations found."); } AnimationClip[] animations = new AnimationClip[animationDictionary.Count]; int animationCount = 0; foreach (AnimationContent animationContent in animationDictionary.Values) { List <Keyframe> keyframes = new List <Keyframe>(); // Each bone has its own channel foreach (string bone in animationContent.Channels.Keys) { AnimationChannel animationChannel = animationContent.Channels[bone]; int boneIndex = boneNameList.IndexOf(bone); foreach (AnimationKeyframe keyframe in animationChannel) { keyframes.Add(new Keyframe( keyframe.Time, boneIndex, keyframe.Transform)); } } // Sort all animation frames by time keyframes.Sort(); animations[animationCount++] = new AnimationClip( animationContent.Name, animationContent.Duration, keyframes.ToArray()); } return(animations); }
/// <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) { //sbLog.Append("Animation Name:" + animation.Key); //sbLog.Append(" Identity: " + animation.Value.Identity); //sbLog.AppendLine(" Duration: " + animation.Value.Duration.ToString()); //sbLog.AppendLine(" Channels.Keys: " + animation.Value.Channels.Values.GetEnumerator().Current. Console.WriteLine("animation.Value.Name=" + animation.Value.Name); 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); }
private AnimationData[] ExtractAnimations(AnimationContentDictionary animationDictionary, List <string> boneNameList, ContentProcessorContext context) { context.Logger.LogImportantMessage("{0} animations found.", animationDictionary.Count); AnimationData[] animations = new AnimationData[animationDictionary.Count]; int count = 0; foreach (AnimationContent animationContent in animationDictionary.Values) { // Store all keyframes of the animation List <Keyframe> keyframes = new List <Keyframe>(); // Go through all animation channels (Each bone has it's own channel) foreach (string animationKey in animationContent.Channels.Keys) { AnimationChannel animationChannel = animationContent.Channels[animationKey]; int boneIndex = boneNameList.IndexOf(animationKey); //context.Logger.LogImportantMessage("{0} - Bone: ", animationKey, boneIndex); foreach (AnimationKeyframe keyframe in animationChannel) { keyframes.Add(new Keyframe(keyframe.Time, boneIndex, keyframe.Transform)); } } context.Logger.LogImportantMessage("Animation {0}: {1} channels found, {2} keyframes found.", animationContent.Name, animationContent.Channels.Count, keyframes.Count); // Sort all animation frames by time keyframes.Sort(); animations[count++] = new AnimationData(animationContent.Name, animationContent.Duration, keyframes.ToArray()); } return(animations); }
/// <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>(); 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)) { context.AddDependency(AnimPath); AnimationDefinition AnimDef = context.BuildAndLoadAsset<XmlImporter, AnimationDefinition>(new ExternalReference<XmlImporter>(AnimPath), null); //breaks up original animation clips into new clips if (animationClips.ContainsKey(AnimDef.originalClipName)) { //graps main clip AnimationClip MainClip = animationClips[AnimDef.originalClipName]; //remove original clip from animations animationClips.Remove(AnimDef.originalClipName); foreach (AnimationDefinition.clipPart Part in AnimDef.ClipParts) { //calculate frame times TimeSpan StartTime = GetTimeSpanForFrame(Part.StartFrame, AnimDef.originalFrameCount, MainClip.Duration.Ticks); TimeSpan EndTime = GetTimeSpanForFrame(Part.EndFrame, AnimDef.originalFrameCount, MainClip.Duration.Ticks); //get keyframes for animation clip thats in 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 events /*List<AnimationEvent> Events = new List<AnimationEvent>(); if (Part.Events != null) { foreach (AnimationDefinition.clipPart.Event Event in Part.Events) { TimeSpan EventTime = GetTimeSpanForFrame(Event.Keyframe, AnimDef.originalFrameCount, MainClip.Duration.Ticks); EventTime -= StartTime; AnimationEvent newEvent = new AnimationEvent(); newEvent.EventTime = EventTime; newEvent.EventName = Event.Name; Events.Add(newEvent); } }*/ AnimationClip newClip = new AnimationClip(EndTime - StartTime, Keyframes, Part.ClipName); animationClips[Part.ClipName] = newClip; } } } /* if (animationClips.Count == 0) { throw new InvalidContentException( "Input file does not contain any animations."); }*/ return animationClips; }
/// <summary> /// Called when an XML document is read that specifies how animations /// should be split. /// </summary> /// <param name="animDict">The dictionary of animation name/AnimationContent /// pairs. </param> /// <param name="doc">The Xml document that contains info on how to split /// the animations.</param> protected virtual void SubdivideAnimations( AnimationContentDictionary animDict, XmlDocument doc) { string[] animNames = new string[animDict.Keys.Count]; animDict.Keys.CopyTo(animNames, 0); if (animNames.Length == 0) { return; } // Traverse each xml node that represents an animation to be subdivided foreach (XmlNode node in doc) { XmlElement child = node as XmlElement; if (child == null || child.Name != "animation") { continue; } string animName = null; if (child["name"] != null) { // The name of the animation to be split animName = child["name"].InnerText; } else if (child["index"] != null) { animName = animNames[int.Parse(child["index"].InnerText)]; } else { animName = animNames[0]; } // If the tickspersecond node is filled, use that to calculate seconds per tick double animTicksPerSecond = 1.0, secondsPerTick = 0; try { if (child["tickspersecond"] != null) { animTicksPerSecond = double.Parse(child["tickspersecond"].InnerText); } } catch { throw new Exception("Error parsing tickspersecond in xml file."); } if (animTicksPerSecond <= 0) { throw new InvalidDataException("AnimTicksPerSecond in XML file must be " + "a positive number."); } secondsPerTick = 1.0 / animTicksPerSecond; AnimationContent anim = null; // Get the animation and remove it from the dict // Check to see if the animation specified in the xml file exists try { anim = animDict[animName]; } catch { throw new Exception("Animation named " + animName + " specified in XML file does not exist in model."); } animDict.Remove(anim.Name); // Get the list of new animations XmlNodeList subAnimations = child.GetElementsByTagName("animationsubset"); foreach (XmlElement subAnim in subAnimations) { // Create the new sub animation AnimationContent newAnim = new AnimationContent(); XmlElement subAnimNameElement = subAnim["name"]; if (subAnimNameElement != null) { newAnim.Name = subAnimNameElement.InnerText; } // If a starttime node exists, use that to get the start time long startTime, endTime; if (subAnim["starttime"] != null) { try { startTime = TimeSpan.FromSeconds(double.Parse(subAnim["starttime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing starttime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["startframe"] != null)// else use the secondspertick combined with the startframe node value { try { double seconds = double.Parse(subAnim["startframe"].InnerText) * secondsPerTick; startTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing startframe node in XML file. Node inner text " + "must be a non negative number."); } } else { throw new Exception("Sub animation in XML file must have either a starttime or startframe node."); } // Same with endtime/endframe if (subAnim["endtime"] != null) { try { endTime = TimeSpan.FromSeconds(double.Parse(subAnim["endtime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing endtime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["endframe"] != null) { try { double seconds = double.Parse(subAnim["endframe"].InnerText) * secondsPerTick; endTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing endframe node in XML file. Node inner text " + "must be a non negative number."); } } else { throw new Exception("Sub animation in XML file must have either an endtime or endframe node."); } if (endTime < startTime) { throw new Exception("Start time must be <= end time in XML file."); } // Now that we have the start and end times, we associate them with // start and end indices for each animation track/channel foreach (KeyValuePair <string, AnimationChannel> k in anim.Channels) { // The current difference between the start time and the // time at the current index long currentStartDiff; // The current difference between the end time and the // time at the current index long currentEndDiff; // The difference between the start time and the time // at the start index long bestStartDiff = long.MaxValue; // The difference between the end time and the time at // the end index long bestEndDiff = long.MaxValue; // The start and end indices int startIndex = -1; int endIndex = -1; // Create a new channel and reference the old channel AnimationChannel newChan = new AnimationChannel(); AnimationChannel oldChan = k.Value; // Iterate through the keyframes in the channel for (int i = 0; i < oldChan.Count; i++) { // Update the startIndex, endIndex, bestStartDiff, // and bestEndDiff long ticks = oldChan[i].Time.Ticks; currentStartDiff = Math.Abs(startTime - ticks); currentEndDiff = Math.Abs(endTime - ticks); if (startIndex == -1 || currentStartDiff < bestStartDiff) { startIndex = i; bestStartDiff = currentStartDiff; } if (endIndex == -1 || currentEndDiff < bestEndDiff) { endIndex = i; bestEndDiff = currentEndDiff; } } // Now we have our start and end index for the channel for (int i = startIndex; i <= endIndex; i++) { AnimationKeyframe frame = oldChan[i]; long time; // Clamp the time so that it can't be less than the // start time if (frame.Time.Ticks < startTime) { time = 0; } // Clamp the time so that it can't be greater than the // end time else if (frame.Time.Ticks > endTime) { time = endTime - startTime; } else // Else get the time { time = frame.Time.Ticks - startTime; } // Finally... create the new keyframe and add it to the new channel AnimationKeyframe keyframe = new AnimationKeyframe( TimeSpan.FromTicks(time), frame.Transform); newChan.Add(keyframe); } // Add the channel and update the animation duration based on the // length of the animation track. newAnim.Channels.Add(k.Key, newChan); if (newChan[newChan.Count - 1].Time > newAnim.Duration) { newAnim.Duration = newChan[newChan.Count - 1].Time; } } try { // Add the subdived animation to the dictionary. animDict.Add(newAnim.Name, newAnim); } catch { throw new Exception("Attempt to add an animation when one by the same name already exists. " + "Name: " + newAnim.Name); } } } }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> Dictionary <string, ClipContent> ProcessAnimations(NodeContent input, ContentProcessorContext context, AnimationContentDictionary animations, IList <BoneContent> bones, int generateKeyframesFrequency) { // 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, ClipContent> animationClips; animationClips = new Dictionary <string, ClipContent>(); foreach (KeyValuePair <string, AnimationContent> animation in animations) { ClipContent clip = ProcessAnimation(input, context, animation.Value, boneMap, generateKeyframesFrequency); animationClips.Add(animation.Key, clip); } if (animationClips.Count == 0) { //throw new InvalidContentException("Input file does not contain any animations."); context.Logger.LogWarning(null, null, "Input file does not contain any animations."); } return(animationClips); }
private static Dictionary<string, AnimationClip> ProcessAnimations(AnimationContentDictionary animations, IList<BoneContent> bones) { bool count; Dictionary<string, int> strs = new Dictionary<string, int>(); int num = 0; while (true) { count = num < bones.Count; if (!count) { break; } string name = bones[num].Name; count = string.IsNullOrEmpty(name); if (!count) { strs.Add(name, num); } num++; } Dictionary<string, AnimationClip> strs1 = new Dictionary<string, AnimationClip>(); IEnumerator<KeyValuePair<string, AnimationContent>> enumerator = animations.GetEnumerator(); try { while (true) { count = enumerator.MoveNext(); if (!count) { break; } KeyValuePair<string, AnimationContent> current = enumerator.Current; AnimationClip animationClip = SkinnedModelProcessor.ProcessAnimation(current.Value, strs); strs1.Add(current.Key, animationClip); } } finally { count = enumerator == null; if (!count) { enumerator.Dispose(); } } count = strs1.Count != 0; if (count) { Dictionary<string, AnimationClip> strs2 = strs1; return strs2; } else { throw new InvalidContentException("Input file does not contain any animations."); } }
/// <summary> /// コンテント・パイプライン内の中間フォーマットである /// AnimationContentDictionaryから、ランタイムフォーマットである /// AnimationClipフォーマットに変換する /// </summary> static Dictionary<string, AnimationClip> ProcessAnimations( AnimationContentDictionary animations, IList<BoneContent> bones) { // ボーン名からインデックスに変換する辞書テーブルを作る 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); } // それぞれのアニメーションを変換する 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( "入力ファイルはアニメーションが含まれていません。"); } return animationClips; }
static Dictionary<String, AnimationClip> ProcessAnimations(AnimationContentDictionary animations, IList<BoneContent> bones) { // Build up a table mapping bones to indices. Dictionary<String, int> boneMap = new Dictionary<String, int>(); for (int i = 0; i < bones.Count; i++) boneMap.Add(bones[i].Name, i); Dictionary<String, AnimationClip> animationsClips = new Dictionary<String, AnimationClip>(); // Convert each animation foreach (KeyValuePair<String, AnimationContent> animation in animations) { // Rotate animation //RotateAll(animation.Value); // Get animation clip AnimationClip processed = ProcessAnimation(animation.Value, boneMap); processed.Name = animation.Key; // Add the clip animationsClips.Add(animation.Key, processed); } // Return the animations clips return animationsClips; }
/// <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) { string[] animationProps = animation.Key.Split('_'); if (animationProps.Length != 2) throw new InvalidContentException(string.Format("[AnimationName: {0}]\r\nYou need to specify animatons like: AnimationName;Frames\r\nAlso make sure your first animation has enough frames to cover the maximum animation length due to an XNA bug.", animation.Key)); string name = animationProps[0]; int frames = int.Parse(animationProps[1]) - 1; animation.Value.Duration = TimeSpan.FromSeconds(frames * SecondsPerFrame); AnimationClip processed = ProcessAnimation(animation.Value, boneMap); animationClips.Add(name, processed); } if (animationClips.Count == 0) { throw new InvalidContentException( "Input file does not contain any animations."); } return animationClips; }
/// <summary> /// Called when an XML document is read that specifies how animations /// should be split. /// </summary> /// <param name="animDict">The dictionary of animation name/AnimationContent /// pairs. </param> /// <param name="doc">The Xml document that contains info on how to split /// the animations.</param> protected virtual void SubdivideAnimations( AnimationContentDictionary animDict, XmlDocument doc) { string[] animNames = new string[animDict.Keys.Count]; animDict.Keys.CopyTo(animNames, 0); if (animNames.Length == 0) return; // Traverse each xml node that represents an animation to be subdivided foreach (XmlNode node in doc) { XmlElement child = node as XmlElement; if (child == null || child.Name != "animation") continue; string animName = null; if (child["name"] != null) { // The name of the animation to be split animName = child["name"].InnerText; } else if (child["index"] != null) { animName = animNames[int.Parse(child["index"].InnerText)]; } else { animName = animNames[0]; } // If the tickspersecond node is filled, use that to calculate seconds per tick double animTicksPerSecond = 1.0, secondsPerTick = 0; try { if (child["tickspersecond"] != null) { animTicksPerSecond = double.Parse(child["tickspersecond"].InnerText); } } catch { throw new Exception("Error parsing tickspersecond in xml file."); } if (animTicksPerSecond <= 0) throw new InvalidDataException("AnimTicksPerSecond in XML file must be " + "a positive number."); secondsPerTick = 1.0 / animTicksPerSecond; AnimationContent anim = null; // Get the animation and remove it from the dict // Check to see if the animation specified in the xml file exists try { anim = animDict[animName]; } catch { throw new Exception("Animation named " + animName + " specified in XML file does not exist in model."); } animDict.Remove(anim.Name); // Get the list of new animations XmlNodeList subAnimations = child.GetElementsByTagName("animationsubset"); foreach (XmlElement subAnim in subAnimations) { // Create the new sub animation AnimationContent newAnim = new AnimationContent(); XmlElement subAnimNameElement = subAnim["name"]; if (subAnimNameElement != null) newAnim.Name = subAnimNameElement.InnerText; // If a starttime node exists, use that to get the start time long startTime, endTime; if (subAnim["starttime"] != null) { try { startTime = TimeSpan.FromSeconds(double.Parse(subAnim["starttime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing starttime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["startframe"] != null)// else use the secondspertick combined with the startframe node value { try { double seconds = double.Parse(subAnim["startframe"].InnerText) * secondsPerTick; startTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing startframe node in XML file. Node inner text " + "must be a non negative number."); } } else throw new Exception("Sub animation in XML file must have either a starttime or startframe node."); // Same with endtime/endframe if (subAnim["endtime"] != null) { try { endTime = TimeSpan.FromSeconds(double.Parse(subAnim["endtime"].InnerText)).Ticks; } catch { throw new Exception("Error parsing endtime node in XML file. Node inner text " + "must be a non negative number."); } } else if (subAnim["endframe"] != null) { try { double seconds = double.Parse(subAnim["endframe"].InnerText) * secondsPerTick; endTime = TimeSpan.FromSeconds( seconds).Ticks; } catch { throw new Exception("Error parsing endframe node in XML file. Node inner text " + "must be a non negative number."); } } else throw new Exception("Sub animation in XML file must have either an endtime or endframe node."); if (endTime < startTime) throw new Exception("Start time must be <= end time in XML file."); // Now that we have the start and end times, we associate them with // start and end indices for each animation track/channel foreach (KeyValuePair<string, AnimationChannel> k in anim.Channels) { // The current difference between the start time and the // time at the current index long currentStartDiff; // The current difference between the end time and the // time at the current index long currentEndDiff; // The difference between the start time and the time // at the start index long bestStartDiff=long.MaxValue; // The difference between the end time and the time at // the end index long bestEndDiff=long.MaxValue; // The start and end indices int startIndex = -1; int endIndex = -1; // Create a new channel and reference the old channel AnimationChannel newChan = new AnimationChannel(); AnimationChannel oldChan = k.Value; // Iterate through the keyframes in the channel for (int i = 0; i < oldChan.Count; i++) { // Update the startIndex, endIndex, bestStartDiff, // and bestEndDiff long ticks = oldChan[i].Time.Ticks; currentStartDiff = Math.Abs(startTime - ticks); currentEndDiff = Math.Abs(endTime - ticks); if (startIndex == -1 || currentStartDiff<bestStartDiff) { startIndex = i; bestStartDiff = currentStartDiff; } if (endIndex == -1 || currentEndDiff<bestEndDiff) { endIndex = i; bestEndDiff = currentEndDiff; } } // Now we have our start and end index for the channel for (int i = startIndex; i <= endIndex; i++) { AnimationKeyframe frame = oldChan[i]; long time; // Clamp the time so that it can't be less than the // start time if (frame.Time.Ticks < startTime) time = 0; // Clamp the time so that it can't be greater than the // end time else if (frame.Time.Ticks > endTime) time = endTime - startTime; else // Else get the time time = frame.Time.Ticks - startTime; // Finally... create the new keyframe and add it to the new channel AnimationKeyframe keyframe = new AnimationKeyframe( TimeSpan.FromTicks(time), frame.Transform); newChan.Add(keyframe); } // Add the channel and update the animation duration based on the // length of the animation track. newAnim.Channels.Add(k.Key, newChan); if (newChan[newChan.Count - 1].Time > newAnim.Duration) newAnim.Duration = newChan[newChan.Count - 1].Time; } try { // Add the subdived animation to the dictionary. animDict.Add(newAnim.Name, newAnim); } catch { throw new Exception("Attempt to add an animation when one by the same name already exists. " + "Name: " + newAnim.Name); } } } }
/// <summary> /// Creates an instance of NodeContent. /// </summary> public NodeContent() { children = new NodeContentCollection(this); animations = new AnimationContentDictionary(); Transform = Matrix.Identity; }
/// <summary> /// Extracts all animations and stores them in a dictionary of timelines. /// </summary> private Dictionary<string, SkeletonKeyFrameAnimation> ProcessAnimations(AnimationContentDictionary animationContentDictionary, Skeleton skeleton, ContentProcessorContext context) { var animations = new Dictionary<string, SkeletonKeyFrameAnimation>(); foreach (var item in animationContentDictionary) { string animationName = item.Key; AnimationContent animationContent = item.Value; // Convert the AnimationContent to a SkeletonKeyFrameAnimation. SkeletonKeyFrameAnimation skeletonAnimation = ProcessAnimation(animationContent, skeleton, context); animations.Add(animationName, skeletonAnimation); } if (animations.Count == 0) context.Logger.LogWarning(null, null, "Skinned model does not contain any animations."); return animations; }
protected virtual void CollectAnimatedBones(AnimationContentDictionary dict, NodeContent bone) { AnimationContentDictionary acd = bone.Animations; if (acd != null) { foreach (string name in acd.Keys) { // merge each animation into the dictionary AnimationContent ac = acd[name]; AnimationContent xac; if (!dict.TryGetValue(name, out xac)) { // create it if we haven't already seen it, and there's something there if (ac.Channels.Count > 0) { xac = ac; dict.Add(name, xac); } } else { // merge the animation content foreach (KeyValuePair<string, AnimationChannel> kvp in ac.Channels) { AnimationChannel ov; if (xac.Channels.TryGetValue(kvp.Key, out ov)) { throw new System.ArgumentException( String.Format("The animation {0} has multiple channels named {1}.", name, kvp.Key)); } xac.Channels.Add(kvp.Key, kvp.Value); } xac.Duration = new TimeSpan((long) (Math.Max(xac.Duration.TotalSeconds, ac.Duration.TotalSeconds) * 1e7)); } } } foreach (NodeContent nc in bone.Children) CollectAnimatedBones(dict, nc); }
/// <summary> /// The workhorse of the animation processor. It loops through all /// animations, all tracks, and all keyframes, and converts to the format /// expected by the runtime animation classes. /// </summary> /// <param name="input">The NodeContent to process. Comes from the base ModelProcessor.</param> /// <param name="output">The ModelContent that was produced. You don't typically change this.</param> /// <param name="context">The build context (logger, etc).</param> /// <returns>An allocated AnimationSet with the animations to include.</returns> public virtual AnimationSet BuildAnimationSet(NodeContent input, ref ModelContent output, ContentProcessorContext context) { AnimationSet ret = new AnimationSet(); if (!DoAnimations) { context.Logger.LogImportantMessage("DoAnimation is set to false for {0}; not generating animations.", input.Name); return(ret); } // go from name to index Dictionary <string, ModelBoneContent> nameToIndex = new Dictionary <string, ModelBoneContent>(); foreach (ModelBoneContent mbc in output.Bones) { nameToIndex.Add(GetBoneName(mbc), mbc); } AnimationContentDictionary adict = MergeAnimatedBones(input); if (adict == null || adict.Count == 0) { context.Logger.LogWarning("http://kwxport.sourceforge.net/", input.Identity, "Model processed with AnimationProcessor has no animations."); return(ret); } foreach (AnimationContent ac in adict.Values) { if (!IncludeAnimation(ac)) { context.Logger.LogImportantMessage(String.Format("Not including animation named {0}.", ac.Name)); continue; } context.Logger.LogImportantMessage( "Processing animation {0} duration {1} sample rate {2} reduction tolerance {3}.", ac.Name, ac.Duration, SampleRate, Tolerance); AnimationChannelDictionary acdict = ac.Channels; AnimationTrackDictionary tracks = new AnimationTrackDictionary(); foreach (string name in acdict.Keys) { if (!IncludeTrack(ac, name)) { context.Logger.LogImportantMessage(String.Format("Not including track named {0}.", name)); continue; } int ix = 0; AnimationChannel achan = acdict[name]; int bix = nameToIndex[name].Index; context.Logger.LogMessage("Processing bone {0}:{1}.", name, bix); AnimationTrack at; if (tracks.TryGetValue(bix, out at)) { throw new System.ArgumentException( String.Format("Bone index {0} is used by multiple animations in the same clip (name {1}).", bix, name)); } // Sample at given frame rate from 0 .. Duration List <Keyframe> kfl = new List <Keyframe>(); int nFrames = (int)Math.Floor(ac.Duration.TotalSeconds * SampleRate + 0.5); for (int i = 0; i < nFrames; ++i) { Keyframe k = SampleChannel(achan, i / SampleRate, ref ix); kfl.Add(k); } // Run keyframe elimitation Keyframe[] frames = kfl.ToArray(); int nReduced = 0; if (tolerance_ > 0) { nReduced = ReduceKeyframes(frames, tolerance_); } if (nReduced > 0) { context.Logger.LogMessage("Reduced '{2}' from {0} to {1} frames.", frames.Length, frames.Length - nReduced, name); } // Create an AnimationTrack at = new AnimationTrack(bix, frames); tracks.Add(bix, at); } Animation a = new Animation(ac.Name, tracks, SampleRate); ret.AddAnimation(a); } // build the special "identity" and "bind pose" animations AnimationTrackDictionary atd_id = new AnimationTrackDictionary(); AnimationTrackDictionary atd_bind = new AnimationTrackDictionary(); foreach (KeyValuePair <string, ModelBoneContent> nip in nameToIndex) { Keyframe[] frames_id = new Keyframe[2]; frames_id[0] = new Keyframe(); frames_id[1] = new Keyframe(); AnimationTrack at_id = new AnimationTrack(nip.Value.Index, frames_id); atd_id.Add(nip.Value.Index, at_id); Keyframe[] frames_bind = new Keyframe[2]; Matrix mat = nip.Value.Transform; frames_bind[0] = Keyframe.CreateFromMatrix(mat); frames_bind[1] = new Keyframe(); frames_bind[1].CopyFrom(frames_bind[0]); AnimationTrack at_bind = new AnimationTrack(nip.Value.Index, frames_bind); atd_bind.Add(nip.Value.Index, at_bind); } ret.AddAnimation(new Animation("$id$", atd_id, 1.0f)); ret.AddAnimation(new Animation("$bind$", atd_bind, 1.0f)); return(ret); }
protected virtual AnimationContentDictionary MergeAnimatedBones(NodeContent root) { AnimationContentDictionary ret = new AnimationContentDictionary(); CollectAnimatedBones(ret, root); return ret; }
/// <summary>Processes a SkinnedModelImporter NodeContent root</summary> /// <param name="input">The root of the X file tree</param> /// <param name="context">The context for this processor</param> /// <returns>A model with animation data on its tag</returns> public override ModelContent Process(NodeContent input, ContentProcessorContext context) { context.Logger.LogImportantMessage("starting acl processing"); ModelSplitter splitter; if (context.TargetPlatform != TargetPlatform.Xbox360) { splitter = new ModelSplitter(input, 56); } else { splitter = new ModelSplitter(input, 40); } modelSplit = splitter.Split(); splitter = null; this.input = input; this.context = context; FindMeshes(input); indexers = new BoneIndexer[numMeshes]; for (int i = 0; i < indexers.Length; i++) { indexers[i] = new BoneIndexer(); } foreach (MeshContent meshContent in meshes) { CreatePaletteIndices(meshContent); } // Get the process model minus the animation data ModelContent c = base.Process(input, context); if (!modelSplit && input.OpaqueData.ContainsKey("AbsoluteMeshTransforms")) { absoluteMeshTransforms = (List <Matrix>)input.OpaqueData["AbsoluteMeshTransforms"]; } else { foreach (MeshContent mesh in meshes) { if (!ValidateMeshSkeleton(mesh)) { context.Logger.LogWarning(null, mesh.Identity, "Warning: Mesh found that has a parent that exists as " + "one of the bones in the skeleton attached to the mesh. Change the mesh " + "skeleton structure or use X - File Animation Library importer if transforms are incorrect."); } } } Dictionary <string, object> dict = new Dictionary <string, object>(); // Attach the animation and skinning data to the models tag FindAnimations(input); // Test to see if any animations have zero duration foreach (AnimationContent anim in animations.Values) { string errorMsg = "One or more AnimationContent objects have an extremely small duration. If the animation " + "was intended to last more than one frame, please add \n AnimTicksPerSecond \n{0} \nY; \n{1}\n to your .X " + "file, where Y is a positive integer."; if (anim.Duration.Ticks < ContentUtil.TICKS_PER_60FPS) { context.Logger.LogWarning("", anim.Identity, errorMsg, "{", "}"); break; } } XmlDocument xmlDoc = ReadAnimationXML(input); if (xmlDoc != null) { SubdivideAnimations(animations, xmlDoc); } AnimationContentDictionary processedAnims = new AnimationContentDictionary(); try { foreach (KeyValuePair <string, AnimationContent> animKey in animations) { AnimationContent processedAnim = ProcessAnimation(animKey.Value); processedAnims.Add(animKey.Key, processedAnim); } dict.Add("Animations", processedAnims); } catch { throw new Exception("Error processing animations."); } foreach (ModelMeshContent meshContent in c.Meshes) { ReplaceBasicEffects(meshContent); } skinInfo = ProcessSkinInfo(c); dict.Add("SkinInfo", skinInfo); c.Tag = dict; return(c); }
public static void Split(AnimationContentDictionary animationDictionary, IList<AnimationSplitDefinition> splits, ContentIdentity contentIdentity, ContentProcessorContext context) { if (splits == null || splits.Count == 0) return; if (animationDictionary == null) return; if (contentIdentity == null) throw new ArgumentNullException("contentIdentity"); if (context == null) throw new ArgumentNullException("context"); if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); // Get first animation. var originalAnimation = animationDictionary.First().Value; // Clear animation dictionary. - We do not keep the original animations! animationDictionary.Clear(); // Add an animation to animationDictionary for each split. foreach (var split in splits) { TimeSpan startTime = split.StartTime; TimeSpan endTime = split.EndTime; var newAnimation = new AnimationContent { Name = split.Name, Duration = endTime - startTime }; // Process all channels. foreach (var item in originalAnimation.Channels) { string channelName = item.Key; AnimationChannel originalChannel = item.Value; if (originalChannel.Count == 0) return; AnimationChannel newChannel = new AnimationChannel(); // Add all key frames to the channel that are in the split interval. foreach (AnimationKeyframe keyFrame in originalChannel) { TimeSpan time = keyFrame.Time; if (startTime <= time && time <= endTime) { newChannel.Add(new AnimationKeyframe(keyFrame.Time - startTime, keyFrame.Transform)); } } // Add channel if it contains key frames. if (newChannel.Count > 0) newAnimation.Channels.Add(channelName, newChannel); } if (newAnimation.Channels.Count == 0) { var message = string.Format(CultureInfo.InvariantCulture, "The split animation '{0}' is empty.", split.Name); throw new InvalidContentException(message, contentIdentity); } if (animationDictionary.ContainsKey(split.Name)) { var message = string.Format(CultureInfo.InvariantCulture, "Cannot add split animation '{0}' because an animation with the same name already exits.", split.Name); throw new InvalidContentException(message, contentIdentity); } animationDictionary.Add(split.Name, newAnimation); } }
/// <summary> /// Creates an instance of NodeContent. /// </summary> public NodeContent() { children = new NodeContentCollection(this); animations = new AnimationContentDictionary(); }
private AnimationClipContentDictionary ProcessAnimations(NodeContent input, AnimationContentDictionary animationDictionary, SkinnedModelBoneContentCollection boneCollection, ContentProcessorContext context) { // Create a collection here (Does not need a dictionary here) Dictionary <string, AnimationClipContent> animationClipDictionary = new Dictionary <string, AnimationClipContent>(); foreach (AnimationContent animation in animationDictionary.Values) { // Validate animation if (!ValidateAnimation(animation, context)) { continue; } Dictionary <string, AnimationChannelContent> animationChannelDictionary = new Dictionary <string, AnimationChannelContent>(); // Process each animation channel (One channel per bone) foreach (KeyValuePair <string, AnimationChannel> animationChannelPair in animation.Channels) { // Validate animation channel if (!ValidateAnimationChannel(animationChannelPair, animation, boneCollection, context)) { continue; } List <AnimationKeyframeContent> keyframeList = new List <AnimationKeyframeContent>(animationChannelPair.Value.Count); // Process all the keyframes of that channel foreach (AnimationKeyframe channelKeyframe in animationChannelPair.Value) { // Extract the keyframe pose from its transform matrix Pose keyframePose; channelKeyframe.Transform.Decompose(out keyframePose.Scale, out keyframePose.Orientation, out keyframePose.Translation); keyframeList.Add( new AnimationKeyframeContent(channelKeyframe.Time, keyframePose)); } // Sort the keyframes by time keyframeList.Sort(); animationChannelDictionary.Add(animationChannelPair.Key, new AnimationChannelContent(keyframeList)); } AnimationClipContent animationClip = new AnimationClipContent( animation.Name, new AnimationChannelContentDictionary(animationChannelDictionary), animation.Duration); animationClipDictionary.Add(animation.Name, animationClip); } // Split animations if (!string.IsNullOrEmpty(splitAnimationFilename)) { SplitAnimations(input, animationClipDictionary, context); } return(new AnimationClipContentDictionary(animationClipDictionary)); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static Dictionary<string, Clip> 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, Clip> animationClips; animationClips = new Dictionary<string, Clip>(); foreach (KeyValuePair<string, AnimationContent> animation in animations) { Clip processed = ProcessAnimation(animation.Value, boneMap); processed.Name = animation.Key; animationClips.Add(animation.Key, processed); } return animationClips; }
public static void Split(AnimationContentDictionary animationDictionary, string splitFile, ContentIdentity contentIdentity, ContentProcessorContext context) { if (animationDictionary == null) { return; } if (string.IsNullOrEmpty(splitFile)) { return; } if (contentIdentity == null) { throw new ArgumentNullException("contentIdentity"); } if (context == null) { throw new ArgumentNullException("context"); } if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) { context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); } // Load XML file. splitFile = ContentHelper.FindFile(splitFile, contentIdentity); XDocument document = XDocument.Load(splitFile, LoadOptions.SetLineInfo); // Let the content pipeline know that we depend on this file and we need to // rebuild the content if the file is modified. context.AddDependency(splitFile); // Parse XML. var animationsElement = document.Element("Animations"); if (animationsElement == null) { context.Logger.LogWarning(null, contentIdentity, "The animation split file \"{0}\" does not contain an <Animations> root node.", splitFile); return; } var wrappedContext = new ContentPipelineContext(context); var splits = ParseAnimationSplitDefinitions(animationsElement, contentIdentity, wrappedContext); if (splits == null || splits.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The XML file with the animation split definitions is invalid or empty. Animation is not split."); return; } // Split animations. Split(animationDictionary, splits, contentIdentity, context); }
public static void Split(AnimationContentDictionary animationDictionary, IList <AnimationSplitDefinition> splits, ContentIdentity contentIdentity, ContentProcessorContext context) { if (splits == null || splits.Count == 0) { return; } if (animationDictionary == null) { return; } if (contentIdentity == null) { throw new ArgumentNullException("contentIdentity"); } if (context == null) { throw new ArgumentNullException("context"); } if (animationDictionary.Count == 0) { context.Logger.LogWarning(null, contentIdentity, "The model does not have an animation. Animation splitting is skipped."); return; } if (animationDictionary.Count > 1) { context.Logger.LogWarning(null, contentIdentity, "The model contains more than 1 animation. The animation splitting is performed on the first animation. Other animations are deleted!"); } // Get first animation. var originalAnimation = animationDictionary.First().Value; // Clear animation dictionary. - We do not keep the original animations! animationDictionary.Clear(); // Add an animation to animationDictionary for each split. foreach (var split in splits) { TimeSpan startTime = split.StartTime; TimeSpan endTime = split.EndTime; var newAnimation = new AnimationContent { Name = split.Name, Duration = endTime - startTime }; // Process all channels. foreach (var item in originalAnimation.Channels) { string channelName = item.Key; AnimationChannel originalChannel = item.Value; if (originalChannel.Count == 0) { return; } AnimationChannel newChannel = new AnimationChannel(); // Add all key frames to the channel that are in the split interval. foreach (AnimationKeyframe keyFrame in originalChannel) { TimeSpan time = keyFrame.Time; if (startTime <= time && time <= endTime) { newChannel.Add(new AnimationKeyframe(keyFrame.Time - startTime, keyFrame.Transform)); } } // Add channel if it contains key frames. if (newChannel.Count > 0) { newAnimation.Channels.Add(channelName, newChannel); } } if (newAnimation.Channels.Count == 0) { var message = string.Format(CultureInfo.InvariantCulture, "The split animation '{0}' is empty.", split.Name); throw new InvalidContentException(message, contentIdentity); } if (animationDictionary.ContainsKey(split.Name)) { var message = string.Format(CultureInfo.InvariantCulture, "Cannot add split animation '{0}' because an animation with the same name already exits.", split.Name); throw new InvalidContentException(message, contentIdentity); } animationDictionary.Add(split.Name, newAnimation); } }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> /// <param name="animations"> /// The animations. /// </param> /// <param name="bones"> /// The bones. /// </param> /// <param name="context"> /// The context. /// </param> /// <param name="source"> /// The source. /// </param> private static Dictionary <string, AnimationClip> ProcessAnimations( AnimationContentDictionary animations, IList <BoneContent> bones, ContentProcessorContext context, string source) { // Build up a table mapping bone names to indices. var 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>(); string xmlFileName = source.Substring(0, source.IndexOf(".fbx")) + "Animation.xml"; if (File.Exists(xmlFileName)) { animationXML = new XmlDocument(); animationXML.Load(xmlFileName); // get all animations XmlNodeList animationList = animationXML.GetElementsByTagName("animation"); string name = string.Empty; int keyframestart = -1, keyframeend = -1; foreach (XmlNode animationNode in animationList) { foreach (XmlNode child in animationNode.ChildNodes) { if (child.Name == "name") { name = child.InnerText; } else if (child.Name == "startframe") { keyframestart = int.Parse(child.InnerText); } else if (child.Name == "endframe") { keyframeend = int.Parse(child.InnerText); } } foreach (var animation in animations) { AnimationClip processed = ProcessAnimation(animation.Value, boneMap, keyframestart, keyframeend); animationClips.Add(name, processed); } } } else { foreach (var 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 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) { if (new[] { "Stand", "Walk", "AttackUnarmed", "Death", "Fly", "Idle", "Run", "ReadySpellCastDirectional", "ReadySpellCastOmni", }.Contains(animation.Key)) { 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 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; }
/// <summary> /// Converts an intermediate format content pipeline AnimationContentDictionary /// object to our runtime AnimationClip format. /// </summary> static Dictionary<string, Clip> ProcessAnimations( AnimationContentDictionary animations, IList<BoneContent> bones, List<ClipData> clipInfo) { // 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, Clip> animationClips = new Dictionary<string, Clip>(); foreach (ClipData clip in clipInfo) { Clip processed = ProcessAnimation(animations[clip.SourceTake], boneMap, clip); animationClips.Add(clip.Alias, processed); } if (animationClips.Count == 0) { throw new InvalidContentException("Input file does not contain any animations."); } return animationClips; }
/// <summary>Processes a SkinnedModelImporter NodeContent root</summary> /// <param name="input">The root of the X file tree</param> /// <param name="context">The context for this processor</param> /// <returns>A model with animation data on its tag</returns> public override ModelContent Process(NodeContent input, ContentProcessorContext context) { ModelSplitter splitter; if (context.TargetPlatform != TargetPlatform.Xbox360) { splitter = new ModelSplitter(input, 56); } else { splitter = new ModelSplitter(input, 40); } modelSplit = splitter.Split(); splitter = null; this.input = input; this.context = context; FindMeshes(input); indexers = new BoneIndexer[numMeshes]; for (int i = 0; i < indexers.Length; i++) { indexers[i] = new BoneIndexer(); } foreach (MeshContent meshContent in meshes) CreatePaletteIndices(meshContent); // Get the process model minus the animation data ModelContent c = base.Process(input, context); if (!modelSplit && input.OpaqueData.ContainsKey("AbsoluteMeshTransforms")) { absoluteMeshTransforms = (List<Matrix>)input.OpaqueData["AbsoluteMeshTransforms"]; } else { foreach (MeshContent mesh in meshes) { if (!ValidateMeshSkeleton(mesh)) { context.Logger.LogWarning(null, mesh.Identity, "Warning: Mesh found that has a parent that exists as " + "one of the bones in the skeleton attached to the mesh. Change the mesh " + "skeleton structure or use X - File Animation Library importer if transforms are incorrect."); } } } Dictionary<string, object> dict = new Dictionary<string, object>(); // Attach the animation and skinning data to the models tag FindAnimations(input); // Test to see if any animations have zero duration foreach (AnimationContent anim in animations.Values) { string errorMsg = "One or more AnimationContent objects have an extremely small duration. If the animation " + "was intended to last more than one frame, please add \n AnimTicksPerSecond \n{0} \nY; \n{1}\n to your .X " + "file, where Y is a positive integer."; if (anim.Duration.Ticks < ContentUtil.TICKS_PER_60FPS) { context.Logger.LogWarning("", anim.Identity, errorMsg, "{", "}"); break; } } XmlDocument xmlDoc = ReadAnimationXML(input); if (xmlDoc != null) { SubdivideAnimations(animations, xmlDoc); } AnimationContentDictionary processedAnims = new AnimationContentDictionary(); try { foreach (KeyValuePair<string, AnimationContent> animKey in animations) { AnimationContent processedAnim = ProcessAnimation(animKey.Value); processedAnims.Add(animKey.Key, processedAnim); } dict.Add("Animations", processedAnims); } catch { throw new Exception("Error processing animations."); } foreach (ModelMeshContent meshContent in c.Meshes) ReplaceBasicEffects(meshContent); skinInfo = ProcessSkinInfo(c); dict.Add("SkinInfo", skinInfo); c.Tag = dict; return c; }
/// <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); }