/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> public static ModelAnimationClip ProcessRootAnimation(AnimationContent animation, string name) { List<ModelKeyframe> keyframes = new List<ModelKeyframe>(); // The root animation is controlling the root of the bones AnimationChannel channel = animation.Channels[name]; // Add the transformations on the root of the model foreach (AnimationKeyframe keyframe in channel) { keyframes.Add(new ModelKeyframe(0, keyframe.Time, keyframe.Transform)); } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); return new ModelAnimationClip(animation.Duration, keyframes); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static Clip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { Dictionary<int, Channel> channels = new Dictionary<int, Channel>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> inputChannel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(inputChannel.Key, out boneIndex)) { throw new InvalidContentException(string.Format( "Found animation for bone '{0}', " + "which is not part of the skeleton.", inputChannel.Key)); } if (inputChannel.Value.Count > 0) { Channel newChannel = null; if (!channels.TryGetValue(boneIndex, out newChannel)) { channels[boneIndex] = newChannel = new Channel(); newChannel.BoneIndex = boneIndex; } foreach (AnimationKeyframe keyframe in inputChannel.Value) { if (newChannel.Count == 0 || !newChannel[newChannel.Count - 1].Transform.Equals(keyframe.Transform)) newChannel.Add(new Keyframe(new TimeSpan(keyframe.Time.Ticks), keyframe.Transform)); } newChannel.Sort(CompareKeyframeTimes); } } return new Clip(new TimeSpan(animation.Duration.Ticks), channels.Values); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap, string clipName) { List<Keyframe> keyframes = new List<Keyframe>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex = boneMap[channel.Key]; // Convert the keyframe data. foreach (AnimationKeyframe keyframe in channel.Value) { keyframes.Add(new Keyframe(boneIndex, keyframe.Time, keyframe.Transform)); } } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); return new AnimationClip(animation.Duration, keyframes, clipName); }
private static AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { int num = 0; bool count; List<Keyframe> keyframes = new List<Keyframe>(); IEnumerator<KeyValuePair<string, AnimationChannel>> enumerator = animation.Channels.GetEnumerator(); try { while (true) { count = enumerator.MoveNext(); if (!count) { break; } KeyValuePair<string, AnimationChannel> current = enumerator.Current; count = boneMap.TryGetValue(current.Key, out num); if (!count) { throw new InvalidContentException(string.Format("Found animation for bone '{0}', which is not part of the skeleton.", current.Key)); } IEnumerator<AnimationKeyframe> enumerator1 = current.Value.GetEnumerator(); try { while (true) { count = enumerator1.MoveNext(); if (!count) { break; } AnimationKeyframe animationKeyframe = enumerator1.Current; keyframes.Add(new Keyframe(num, animationKeyframe.Time, animationKeyframe.Transform)); } } finally { count = enumerator1 == null; if (!count) { enumerator1.Dispose(); } } } } finally { count = enumerator == null; if (!count) { enumerator.Dispose(); } } keyframes.Sort(SkinnedModelProcessor.CompareKeyframeTimes); count = keyframes.Count != 0; if (count) { count = !(animation.Duration <= TimeSpan.Zero); if (count) { AnimationClip animationClip = new AnimationClip(animation.Duration, keyframes); return animationClip; } else { throw new InvalidContentException("Animation has a zero duration."); } } else { throw new InvalidContentException("Animation has no keyframes."); } }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { //System.Diagnostics.Debugger.Launch(); List<Keyframe> keyframes = new List<Keyframe>(); int affectedBones = animation.Channels.Count; int[] affectedBoneIndices = new int[affectedBones]; int affectedBoneIndex = 0; // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { throw new InvalidContentException(string.Format( "Found animation for bone '{0}', " + "which is not part of the skeleton.", channel.Key)); } // save the index of the affected bone affectedBoneIndices[affectedBoneIndex++] = boneIndex; // Convert the keyframe data. foreach (AnimationKeyframe keyframe in channel.Value) { keyframes.Add(new Keyframe(boneIndex, keyframe.Time, keyframe.Transform)); } } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); AnimationClip clip = new AnimationClip(animation.Duration, keyframes, affectedBoneIndices); return clip; }
/// <summary> /// Converts an AnimationContent to a SkeletonKeyFrameAnimation. /// </summary> private SkeletonKeyFrameAnimation ProcessAnimation(AnimationContent animationContent, Skeleton skeleton, ContentProcessorContext context) { var animation = new SkeletonKeyFrameAnimation { EnableInterpolation = true }; // Process all animation channels (each channel animates a bone). int numberOfKeyFrames = 0; foreach (var item in animationContent.Channels) { string channelName = item.Key; AnimationChannel channel = item.Value; int boneIndex = skeleton.GetIndex(channelName); if (boneIndex == -1) { var message = string.Format("Found animation for bone '{0}', which is not part of the skeleton.", channelName); throw new InvalidContentException(message, animationContent.Identity); } var bindPoseRelativeInverse = skeleton.GetBindPoseRelative(boneIndex).Inverse; foreach (AnimationKeyframe keyframe in channel) { TimeSpan time = keyframe.Time; SrtTransform transform = SrtTransform.FromMatrix(keyframe.Transform); // The matrix in the key frame is the transformation in the coordinate space of the // parent bone. --> Convert it to a transformation relative to the animated bone. transform = bindPoseRelativeInverse * transform; // To start with minimal numerical errors, we normalize the rotation quaternion. transform.Rotation.Normalize(); animation.AddKeyFrame(boneIndex, time, transform); numberOfKeyFrames++; } } if (numberOfKeyFrames == 0) throw new InvalidContentException("Animation has no keyframes.", animationContent.Identity); // Compress animation to safe memory. float removedKeyFrames = animation.Compress( CompressionScaleThreshold, CompressionRotationThreshold, CompressionTranslationThreshold); if (removedKeyFrames > 0) context.Logger.LogImportantMessage("{0}: Compression removed {1:P} of all key frames.", animationContent.Name, removedKeyFrames); // Finalize the animation. (Optimizes the animation data for fast runtime access.) animation.Freeze(); return animation; }
private SkeletonKeyFrameAnimation BuildAnimation(AnimationContent animationContent) { string name = animationContent.Name; // Add loop frame? bool addLoopFrame = false; if (_modelDescription != null) { var animationDescription = _modelDescription.Animation; if (animationDescription != null) { addLoopFrame = animationDescription.AddLoopFrame ?? false; if (animationDescription.Splits != null) { foreach (var split in animationDescription.Splits) { if (split.Name == name) { if (split.AddLoopFrame.HasValue) addLoopFrame = split.AddLoopFrame.Value; break; } } } } } var animation = new SkeletonKeyFrameAnimation { EnableInterpolation = true }; // Process all animation channels (each channel animates a bone). int numberOfKeyFrames = 0; foreach (var item in animationContent.Channels) { string channelName = item.Key; AnimationChannel channel = item.Value; int boneIndex = _skeleton.GetIndex(channelName); if (boneIndex != -1) { SrtTransform? loopFrame = null; var bindPoseRelativeInverse = _skeleton.GetBindPoseRelative(boneIndex).Inverse; foreach (AnimationKeyframe keyframe in channel) { TimeSpan time = keyframe.Time; SrtTransform transform = SrtTransform.FromMatrix(keyframe.Transform); // The matrix in the key frame is the transformation in the coordinate space of the // parent bone. --> Convert it to a transformation relative to the animated bone. transform = bindPoseRelativeInverse * transform; // To start with minimal numerical errors, we normalize the rotation quaternion. transform.Rotation.Normalize(); if (loopFrame == null) loopFrame = transform; if (!addLoopFrame || time < animationContent.Duration) animation.AddKeyFrame(boneIndex, time, transform); numberOfKeyFrames++; } if (addLoopFrame && loopFrame.HasValue) animation.AddKeyFrame(boneIndex, animationContent.Duration, loopFrame.Value); } else { _context.Logger.LogWarning( null, animationContent.Identity, "Found animation for bone \"{0}\", which is not part of the skeleton.", channelName); } } if (numberOfKeyFrames == 0) { _context.Logger.LogWarning(null, animationContent.Identity, "Animation is ignored because it has no keyframes."); return null; } // Compress animation to save memory. if (_modelDescription != null) { var animationDescription = _modelDescription.Animation; if (animationDescription != null) { float removedKeyFrames = animation.Compress( animationDescription.ScaleCompression, animationDescription.RotationCompression, animationDescription.TranslationCompression); if (removedKeyFrames > 0) { _context.Logger.LogImportantMessage("{0}: Compression removed {1:P} of all key frames.", string.IsNullOrEmpty(name) ? "Unnamed" : name, removedKeyFrames); } } } // Finalize the animation. (Optimizes the animation data for fast runtime access.) animation.Freeze(); return animation; }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> public static AnimationClip ProcessAnimation(AnimationContent animation, IDictionary<string, int> boneMap, ContentProcessorContext context) { List<Keyframe> keyframes = new List<Keyframe>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { //throw new InvalidContentException(string.Format( // "Found animation for bone '{0}', " + //"which is not part of the skeleton.", channel.Key)); context.Logger.LogWarning(null, null, string.Format("Found animation for bone '{0}', " + "which is not part of the skeleton.", channel.Key)); // Skip this channel continue; } // Convert the keyframe data. foreach (AnimationKeyframe keyframe in channel.Value) { keyframes.Add(new Keyframe(boneIndex, keyframe.Time, keyframe.Transform)); } } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) { //throw new InvalidContentException("Animation has no keyframes."); context.Logger.LogWarning(null, null, "Animation has no keyframes."); return null; } if (animation.Duration <= TimeSpan.Zero) { //throw new InvalidContentException("Animation has a zero duration."); context.Logger.LogWarning(null, null, "Animation has a zero duration."); return null; } // A model from a 3D modelling package does not have sound cues // We have to add them manually to our modified clip file List<TimeSpan> dummy = new List<TimeSpan>(); return new AnimationClip(boneMap.Count, animation.Duration, keyframes, dummy); }
/// <summary> /// Return true if you want to trim the given animation, removing identical frames /// from the end. This is necessay for short animations imported through the .X /// importer, which somehow manages to add blank padding to the duration. /// </summary> /// <param name="ac">The animation to test against the list of animation name patterns</param> /// <returns></returns> protected virtual bool ShouldTrimAnimation(AnimationContent ac) { if (trimAnimationExpressions_ != null) foreach (Regex re in trimAnimationExpressions_) if (re.IsMatch(ac.Name)) return true; return false; }
/// <summary> /// Return true if you want to include the specific animation in the output animation set. /// </summary> /// <param name="ac">The animation to check.</param> /// <returns>true unless the animation name is in a semicolon-separated list called ExcludeAnimations</returns> protected virtual bool IncludeAnimation(AnimationContent ac) { if (excludeAnimationsExpressions_ != null) foreach (Regex re in excludeAnimationsExpressions_) if (re.IsMatch(ac.Name)) return false; return true; }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static Clip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { Dictionary<int, Channel> channels = new Dictionary<int, Channel>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> inputChannel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(inputChannel.Key, out boneIndex)) continue; if (inputChannel.Value.Count > 0) { Channel newChannel = null; if (!channels.TryGetValue(boneIndex, out newChannel)) { channels[boneIndex] = newChannel = new Channel(); newChannel.BoneIndex = boneIndex; } foreach (AnimationKeyframe keyframe in inputChannel.Value) newChannel.Add(new Keyframe(keyframe.Time, keyframe.Transform)); newChannel.Sort(CompareKeyframeTimes); for (int i = 2; i < newChannel.Count; i++) { Keyframe keyframe = newChannel[i]; if (newChannel[i - 1].Transform.Equals(keyframe.Transform) && newChannel[newChannel.Count - 2].Transform.Equals(keyframe.Transform)) { newChannel.RemoveAt(i - 1); i--; } } } } return new Clip(channels.Values); }
/// <summary> /// Return true if you want to include the specific track (bone) in the output animation set. /// </summary> /// <param name="ac">The animation to check.</param> /// <returns>true unless the bone name is in a semicolon-separated list called ExcludeBones</returns> protected virtual bool IncludeTrack(AnimationContent ac, string name) { if (excludeBonesExpressions_ != null) foreach (Regex re in excludeBonesExpressions_) if (re.IsMatch(name)) return false; return true; }
public void DefaultEffectTest() { NodeContent input; { input = new NodeContent(); var mesh = new MeshContent() { Name = "Mesh1" }; mesh.Positions.Add(new Vector3(0, 0, 0)); mesh.Positions.Add(new Vector3(1, 0, 0)); mesh.Positions.Add(new Vector3(1, 1, 1)); var geom = new GeometryContent(); geom.Vertices.Add(0); geom.Vertices.Add(1); geom.Vertices.Add(2); geom.Indices.Add(0); geom.Indices.Add(1); geom.Indices.Add(2); geom.Vertices.Channels.Add(VertexChannelNames.TextureCoordinate(0), new[] { new Vector2(0,0), new Vector2(1,0), new Vector2(1,1), }); var wieghts = new BoneWeightCollection(); wieghts.Add(new BoneWeight("bone1", 0.5f)); geom.Vertices.Channels.Add(VertexChannelNames.Weights(0), new[] { wieghts, wieghts, wieghts }); mesh.Geometry.Add(geom); input.Children.Add(mesh); var bone1 = new BoneContent { Name = "bone1", Transform = Matrix.CreateTranslation(0,1,0) }; input.Children.Add(bone1); var anim = new AnimationContent() { Name = "anim1", Duration = TimeSpan.Zero }; input.Animations.Add(anim.Name, anim); } var processorContext = new TestProcessorContext(TargetPlatform.Windows, "dummy.xnb"); var processor = new ModelProcessor { DefaultEffect = MaterialProcessorDefaultEffect.SkinnedEffect, }; var output = processor.Process(input, processorContext); // TODO: Not sure why, but XNA always returns a BasicMaterialContent // even when we specify SkinnedEffect as the default. We need to fix // the test first before we can enable the assert here. //Assert.IsInstanceOf(typeof(SkinnedMaterialContent), output.Meshes[0].MeshParts[0].Material); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { List<Keyframe> keyframes = new List<Keyframe>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { throw new InvalidContentException(string.Format( "Found animation for bone '{0}', " + "which is not part of the skeleton.", channel.Key)); } // Convert the keyframe data. foreach (AnimationKeyframe keyframe in channel.Value) { Matrix m = keyframe.Transform; m.Translation = m.Translation * Scale; keyframe.Transform = m; keyframes.Add(new Keyframe(boneIndex, keyframe.Time, keyframe.Transform)); } } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); return new AnimationClip(animation.Duration, keyframes); }
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> /// Called when an AnimationContent is processed. /// </summary> /// <param name="animation">The AnimationContent to be processed.</param> /// <returns>The processed AnimationContent.</returns> protected virtual AnimationContent ProcessAnimation(AnimationContent animation) { // M * M = F // AnimationProcessor ap = new AnimationProcessor(); AnimationContent newAnim = ap.Interpolate(animation); newAnim.Name = animation.Name; return newAnim; ; }
/// <summary> /// コンテント・パイプライン内の中間フォーマットである /// AnimationContentから、ランタイムフォーマットである /// AnimationClipフォーマットに変換する /// </summary> static AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { List<Keyframe> keyframes = new List<Keyframe>(); // それぞれのアニメーション・チャンネルを処理する foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // このチャンネルはどのボーンのものか? int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { throw new InvalidContentException(string.Format( "スケルトンに属さないボーン'{0}'"+ "のアニメーションが見つかりました。", channel.Key)); } // キーフレームの変換 foreach (AnimationKeyframe keyframe in channel.Value) { keyframes.Add(new Keyframe(boneIndex, keyframe.Time, QuatTransform.FromMatrix(keyframe.Transform))); } } // キーフレームを時間順に並べ替える keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException( "キーフレームの無いアニメーションです。"); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException( "再生時間が0のアニメーションです。"); return new AnimationClip(animation.Duration, keyframes); }
/// <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 AnimationContent /// object to our runtime AnimationClip format. /// </summary> static AnimationClip ProcessAnimation(AnimationContent animation, Dictionary<string, int> boneMap) { List<Keyframe> keyframes = new List<Keyframe>(); // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { continue; //throw new InvalidContentException(string.Format( // "Found animation for bone '{0}', " + // "which is not part of the skeleton.", channel.Key)); } // Convert the keyframe data. foreach (AnimationKeyframe keyframe in channel.Value.Take(channel.Value.Count - 1)) { keyframes.Add(new Keyframe(boneIndex, keyframe.Time, keyframe.Transform)); } keyframes.Add(new Keyframe(boneIndex, channel.Value.Last().Time, channel.Value.First().Transform)); } // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); if (animation.Duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); return new AnimationClip(/*animation.Duration*/ keyframes.Max(x => x.Time), keyframes); }
/// <summary> /// Converts an intermediate format content pipeline AnimationContent /// object to our runtime AnimationClip format. /// </summary> static Clip ProcessAnimation( AnimationContent animation, Dictionary<string, int> boneMap, ClipData clipData) { List<Keyframe> keyframes = new List<Keyframe>(); Dictionary<TimeSpan, AnimationControlEvents> controlEvents = new Dictionary<TimeSpan,AnimationControlEvents>(); bool frameTimesFilled = false; TimeSpan[] frameTimes = new TimeSpan[clipData.LastFrame - clipData.FirstFrame + 1]; TimeSpan syncTime = TimeSpan.Zero; // For each input animation channel. foreach (KeyValuePair<string, AnimationChannel> channel in animation.Channels) { // Look up what bone this channel is controlling. int boneIndex; if (!boneMap.TryGetValue(channel.Key, out boneIndex)) { // string.Format("Found animation for bone '{0}', which is not part of the skeleton.", channel.Key)); // We can just ignore these channels. continue; } if (!frameTimesFilled) { // I guess the frames in the ClipData are specified with a 0-based index? TimeSpan startTime = channel.Value[clipData.FirstFrame].Time; for (int i = clipData.FirstFrame; i <= clipData.LastFrame; i++) { frameTimes[i - clipData.FirstFrame] = channel.Value[i].Time - startTime; // Also, let's get the times for the events: foreach (KeyValuePair<int, AnimationControlEvents> ace in clipData.Events) { if (ace.Key == i) { controlEvents.Add(frameTimes[i - clipData.FirstFrame], ace.Value); } } // Don't forget to convert the sync frame offset into a time: if (clipData.SyncFrameOffset == i - clipData.FirstFrame) syncTime = frameTimes[i - clipData.FirstFrame]; } if (controlEvents.Count != clipData.Events.Count) throw new InvalidContentException("The time index of some control events could not be determined."); frameTimesFilled = true; } // Convert the keyframe data. for (int i = clipData.FirstFrame; i <= clipData.LastFrame; i++) { Matrix boneTransform; if (channel.Value.Count <= i) { boneTransform = channel.Value[channel.Value.Count - 1].Transform; } else { boneTransform = channel.Value[i].Transform; } keyframes.Add(new Keyframe(boneIndex, frameTimes[i - clipData.FirstFrame], boneTransform)); } } if (keyframes.Count == 0) throw new InvalidContentException("Animation has no keyframes."); TimeSpan framePeriod = TimeSpan.Zero; if (keyframes.Count > 1) framePeriod = keyframes[1].Time - keyframes[0].Time; // Sort the merged keyframes by time. keyframes.Sort(CompareKeyframeTimes); TimeSpan duration = keyframes[keyframes.Count - 1].Time + framePeriod; if (duration <= TimeSpan.Zero) throw new InvalidContentException("Animation has a zero duration."); return new Clip(duration, keyframes, clipData.Loopable, controlEvents, syncTime); }