internal void Load(string name, float frameRate, int numFrames, AnimationTrackDictionary tracks) { name_ = name; frameRate_ = frameRate; numFrames_ = numFrames; tracks_ = tracks; }
public Animation(string name, AnimationTrackDictionary tracks, float frameRate, int durationFrames) { Load(name, frameRate, durationFrames, tracks); if (durationFrames < 0) { CalcNumFrames(); } }
protected override Animation Read(ContentReader input, Animation existingInstance) { if (existingInstance == null) { existingInstance = new Animation(); } string name = input.ReadString(); float frameRate = input.ReadSingle(); int numFrames = input.ReadInt32(); AnimationTrackDictionary tracks = input.ReadObject <AnimationTrackDictionary>(); existingInstance.Load(name, frameRate, numFrames, tracks); return(existingInstance); }
/// <summary> /// Given the animation tracks in the dictionary (for an animation with the given /// name), figure out what the latest frame is that contains unique data for any /// track, and trim the animation from the end to that length. /// </summary> /// <param name="name">name of the animation to trim</param> /// <param name="tracks">the tracks of the animation to trim</param> /// <param name="context">for logging etc</param> protected virtual void TrimAnimationTracks(string name, AnimationTrackDictionary tracks, ContentProcessorContext context) { int latestUnique = 1; int latestFrame = 0; foreach (AnimationTrack at in tracks.Values) { Keyframe last = at.Keyframes[0]; int latestCurrent = 0; int index = 0; if (at.NumFrames > latestFrame) { latestFrame = at.NumFrames; } foreach (Keyframe kf in at.Keyframes) { if (kf != null && last.DifferenceFrom(kf) >= tolerance_) { latestCurrent = index; last = kf; } ++index; } if (latestCurrent > latestUnique) { latestUnique = latestCurrent; } } if (latestUnique + 1 < latestFrame) { context.Logger.LogMessage("Trimming animation {0} from {1} to {2} frames.", name, latestFrame, latestUnique + 1); foreach (AnimationTrack at in tracks.Values) { at.ChopToLength(latestUnique + 1); } } }
/// <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); }
/// <summary> /// Given the animation tracks in the dictionary (for an animation with the given /// name), figure out what the latest frame is that contains unique data for any /// track, and trim the animation from the end to that length. /// </summary> /// <param name="name">name of the animation to trim</param> /// <param name="tracks">the tracks of the animation to trim</param> /// <param name="context">for logging etc</param> protected virtual void TrimAnimationTracks(string name, AnimationTrackDictionary tracks, ContentProcessorContext context) { int latestUnique = 1; int latestFrame = 0; foreach (AnimationTrack at in tracks.Values) { Keyframe last = at.Keyframes[0]; int latestCurrent = 0; int index = 0; if (at.NumFrames > latestFrame) { latestFrame = at.NumFrames; } foreach (Keyframe kf in at.Keyframes) { if (kf != null && last.DifferenceFrom(kf) >= tolerance_) { latestCurrent = index; last = kf; } ++index; } if (latestCurrent > latestUnique) { latestUnique = latestCurrent; } } if (latestUnique + 1 < latestFrame) { context.Logger.LogMessage("Trimming animation {0} from {1} to {2} frames.", name, latestFrame, latestUnique + 1); foreach (AnimationTrack at in tracks.Values) { at.ChopToLength(latestUnique + 1); } } }
/// <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(); TimeSpan longestUniqueDuration = new TimeSpan(0); foreach (string name in acdict.Keys) { if (!IncludeTrack(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); Debug.Assert(name != null); at.Name = name; tracks.Add(bix, at); } if (ShouldTrimAnimation(ac)) { TrimAnimationTracks(ac.Name, tracks, context); } 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) { if (!IncludeTrack(nip.Key)) continue; 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); at_id.Name = nip.Key; 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); at_bind.Name = nip.Key; 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; }
public Animation(string name, AnimationTrackDictionary tracks, float frameRate) : this(name, tracks, frameRate, -1) { }