static SerializableAnimation ProcessAnimation(AnimationContent xnaAnimation) { SerializableAnimation animation = new SerializableAnimation(); animation.name = xnaAnimation.Name; animation.length = (float)xnaAnimation.Duration.TotalSeconds; foreach (KeyValuePair <string, AnimationChannel> xnaAnimationChannelPair in xnaAnimation.Channels) { AnimationChannel xnaAnimationChannel = xnaAnimationChannelPair.Value; string xnaAnimationChannelName = xnaAnimationChannelPair.Key; SerializableTrack track = new SerializableTrack(); track.name = xnaAnimationChannelName; animation.tracks.Add(track); for (int i = 0; i < xnaAnimationChannel.Count; i++) { AnimationKeyframe xnaKeyframe = xnaAnimationChannel[i]; SerializableKeyFrame keyframe = new SerializableKeyFrame((float)xnaKeyframe.Time.TotalSeconds, xnaKeyframe.Transform); track.AddKeyFrame(keyframe); } } return(animation); }
/// <summary> /// Writes a ModelInfo object into XNB data /// </summary> /// <param name="output">The stream that contains the written data</param> /// <param name="value">The instance to be serialized</param> protected override void Write(ContentWriter output, AnimationContentDictionary value) { AnimationContentDictionary animations = value; output.Write(animations.Count); foreach (KeyValuePair <string, AnimationContent> k in animations) { output.Write(k.Key); output.Write(k.Value.Channels.Count); foreach (KeyValuePair <string, AnimationChannel> chan in k.Value.Channels) { output.Write(chan.Key); int numKeys = chan.Value.Count; int halfKeys = numKeys > 0 ? numKeys / 2 + 1 : 0; long duration = chan.Value.Count > 0 ? chan.Value[chan.Value.Count - 1].Time.Ticks : 0; output.Write(halfKeys); output.Write(duration); for (int i = 0; i < halfKeys; ++i) { int idx = i * 2 < numKeys ? i * 2 : numKeys - 1; AnimationKeyframe keyframe = chan.Value[idx]; output.Write(keyframe.Transform); } } } }
/* * template AnimationKey * { * DWORD keyType; * DWORD nKeys; * array TimedFloatKeys keys[nKeys]; * } * * * template TimedFloatKeys * { * DWORD time; * FloatKeys tfkeys; * } * * template FloatKeys * { * DWORD nValues; * array float values[nValues]; * } */ /// <summary> /// Imports a key frame list associated with an animation channel /// </summary> /// <param name="keyType">The type of animation keys used by the current channel</param> /// <returns>The list of key frames for the given key type in the current channel</returns> private AnimationKeyframe[] ImportAnimationKey(out int keyType) { // These keys can be rotation (0),scale(1),translation(2), or matrix(3 or 4) keys. keyType = tokens.SkipName().NextInt(); // Number of frames in channel int numFrames = tokens.NextInt(); AnimationKeyframe[] frames = new AnimationKeyframe[numFrames]; // Find the ticks per millisecond that defines how fast the animation should go double ticksPerMS = animTicksPerSecond == null ? DEFAULT_TICKS_PER_SECOND / 1000.0 : (double)animTicksPerSecond / 1000.0; // fill in the frames for (int i = 0; i < numFrames; i++) { // Create a timespan object that represents the time that the current keyframe // occurs TimeSpan time = new TimeSpan(0, 0, 0, 0, (int)(tokens.NextInt() / ticksPerMS)); // The number of keys that represents the transform for this keyframe. // Quaternions (rotation keys) have 4, // Vectors (scale and translation) have 3, // Matrices have 16 int numKeys = tokens.NextInt(); Matrix transform = new Matrix(); if (numKeys == 16) { transform = tokens.NextMatrix(); } else if (numKeys == 4) { Vector4 v = tokens.NextVector4(); Quaternion q = new Quaternion( new Vector3(-v.Y, -v.Z, -v.W), v.X); transform = Matrix.CreateFromQuaternion(q); } else if (numKeys == 3) { Vector3 v = tokens.NextVector3(); if (keyType == 1) { Matrix.CreateScale(ref v, out transform); } else { Matrix.CreateTranslation(ref v, out transform); } } tokens.SkipToken(); frames[i] = new AnimationKeyframe(time, transform); } tokens.SkipToken(); return(frames); }
private void listKeyframes_SelectedIndexChanged(object sender, EventArgs e) { int index = listKeyframes.SelectedIndex; if (index >= 0) { AnimationKeyframe f = (AnimationKeyframe)listKeyframes.SelectedItem; numFrame.Value = f.Index + 1; } }
private static void InitializeFrames(ref AnimationKeyframe[] frames) { SortFrames(ref frames); if (frames[0].Time != TimeSpan.Zero) { AnimationKeyframe[] newFrames = new AnimationKeyframe[frames.Length + 1]; Array.ConstrainedCopy(frames, 0, newFrames, 1, frames.Length); newFrames[0] = frames[0]; frames = newFrames; } }
/// <summary> /// Create an AvatarRenderer-friendly matrix from an animation keyframe. /// </summary> /// <param name="keyframe">The keyframe to be converted.</param> /// <param name="boneIndex">The index of the bone this keyframe is for.</param> /// <returns>The converted AvatarRenderer-friendly matrix for this bone and keyframe.</returns> private Matrix CreateKeyFrameMatrix(AnimationKeyframe keyframe, int boneIndex) { // safety-check the parameter if (keyframe == null) { throw new ArgumentNullException("keyframe"); } // Retrieve the transform for this keyframe Matrix keyframeMatrix; // The root node is transformed by the root of the bind pose // We need to make the keyframe relative to the root if (boneIndex == 0) { // When the animation is exported the bind pose can have the // wrong translation of the root node so we hard code it here Vector3 bindPoseTranslation = new Vector3(0.000f, 75.5199f, -0.8664f); Matrix keyTransfrom = keyframe.Transform; Matrix inverseBindPose = _bindPoses[boneIndex]; inverseBindPose.Translation -= bindPoseTranslation; inverseBindPose = Matrix.Invert(inverseBindPose); keyframeMatrix = (keyTransfrom * inverseBindPose); keyframeMatrix.Translation -= bindPoseTranslation; // Scale from cm to meters keyframeMatrix.Translation *= 0.01f; } else { keyframeMatrix = keyframe.Transform; // Only the root node can have translation keyframeMatrix.Translation = Vector3.Zero; } return(keyframeMatrix); }
List <AnimationContent> CreateAnimations() { var animations = new List <AnimationContent>(); for (int i = 0; i < collada.JointAnimations.Count; i++) { var sourceAnim = collada.JointAnimations[i]; AnimationContent animation = new AnimationContent(); animation.Name = sourceAnim.Name ?? String.Format("Take {0:000}", (i + 1)); animation.Duration = TimeSpan.FromSeconds(sourceAnim.EndTime - sourceAnim.StartTime); foreach (var sourceChannel in sourceAnim.Channels) { AnimationChannel channel = new AnimationChannel(); // Adds the different keyframes to the animation channel // NOTE: Might be better to sample the keyframes foreach (var sourceKeyframe in sourceChannel.Sampler.Keyframes) { TimeSpan time = TimeSpan.FromSeconds(sourceKeyframe.Time); Matrix transform = sourceKeyframe.Transform; AnimationKeyframe keyframe = new AnimationKeyframe(time, transform); channel.Add(keyframe); } String key = GetJointKey(sourceChannel.Target); animation.Channels.Add(key, channel); } animation.OpaqueData.Add("FPS", sourceAnim.FramesPerSecond); animations.Add(animation); } return(animations); }
/// <summary> /// SampleChannel will be called to sample the transformation of a given channel /// at a given time. The given index parameter is for use by the sample function, /// to avoid having to look for the "base" frame each call. It will start out as /// zero in the first call for a given channel. "time" will be monotonically /// increasing for a given channel. /// </summary> /// <param name="achan">The channel to sample from.</param> /// <param name="time">The time to sample at (monotonically increasing).</param> /// <param name="ix">For use by SampleChannel (starts at 0 for each new channel).</param> /// <returns>The sampled keyframe output (allocated by this function).</returns> protected virtual Keyframe SampleChannel(AnimationChannel achan, float time, ref int ix) { Keyframe ret = new Keyframe(); AnimationKeyframe akf0 = achan[ix]; float scale = CalcTransformScale(akf0.Transform); //todo: really should be done in world space, but I'm giving up now float offset = akf0.Transform.Translation.Length(); if (scale > maxScale_) { maxScale_ = scale; } if (offset > maxOffset_) { maxOffset_ = offset; } again: if (ix == achan.Count - 1) { return(KeyframeFromMatrix(akf0.Transform, ret)); } AnimationKeyframe akf1 = achan[ix + 1]; if (akf1.Time.TotalSeconds <= time) { akf0 = akf1; ++ix; goto again; } KeyframeFromMatrix(akf0.Transform, tmpA_); KeyframeFromMatrix(akf1.Transform, tmpB_); Keyframe.Interpolate(tmpA_, tmpB_, (float)((time - akf0.Time.TotalSeconds) / (akf1.Time.TotalSeconds - akf0.Time.TotalSeconds)), ret); return(ret); }
/// <summary> /// Merges scale, translation, and rotation keyframes into matrix keyframes. /// </summary> /// <param name="scale">The scale keyframes.</param> /// <param name="translation">The translation keyframes.</param> /// <param name="rotation">The rotation keyframes.</param> /// <returns>The merged matrix keyframes.</returns> public static List <AnimationKeyframe> MergeKeyFrames(AnimationKeyframe[] scale, AnimationKeyframe[] translation, AnimationKeyframe[] rotation) { if (scale == null) { scale = new AnimationKeyframe[] { new AnimationKeyframe(new TimeSpan(0), Matrix.Identity) }; } if (translation == null) { throw new Exception("Animation data is not stored as matrices and " + "has no translation component."); } if (rotation == null) { throw new Exception("Animation data is not stored as matrices and " + "has no rotation component"); } // Sort the frames by time and make sure they start at time 0 and // have length >= 1 InitializeFrames(ref scale); InitializeFrames(ref translation); InitializeFrames(ref rotation); // Get a sorted list of the timespans for all 3 keyframe types, // not counting duplicates SortedList <TimeSpan, object> keyframeTimes = new SortedList <TimeSpan, object>(); foreach (AnimationKeyframe frame in scale) { if (!keyframeTimes.ContainsKey(frame.Time)) { keyframeTimes.Add(frame.Time, null); } } foreach (AnimationKeyframe frame in translation) { if (!keyframeTimes.ContainsKey(frame.Time)) { keyframeTimes.Add(frame.Time, null); } } foreach (AnimationKeyframe frame in rotation) { if (!keyframeTimes.ContainsKey(frame.Time)) { keyframeTimes.Add(frame.Time, null); } } IList <TimeSpan> times = keyframeTimes.Keys; // Allocate the interpolated frame matrices Matrix[] newScales = new Matrix[keyframeTimes.Count]; Matrix[] newTrans = new Matrix[keyframeTimes.Count]; Matrix[] newRot = new Matrix[keyframeTimes.Count]; List <AnimationKeyframe> returnFrames = new List <AnimationKeyframe>(); // Interpolate the frames based on the times InterpFrames(ref scale, ref newScales, times); InterpFrames(ref translation, ref newTrans, times); InterpFrames(ref rotation, ref newRot, times); // Merge the 3 keyframe types into one. for (int i = 0; i < times.Count; i++) { Matrix m = newRot[i]; m = m * newTrans[i]; m = newScales[i] * m; returnFrames.Add(new AnimationKeyframe(times[i], m)); } return(returnFrames); }
/// <summary> /// Imports BVH (Biovision hierarchical) animation data. /// Stores animation data in root bone. /// </summary> public override BoneContent Import(string filename, ContentImporterContext context) { this.context = context; contentId = new ContentIdentity(filename, GetType().ToString()); AnimationContent animation = new AnimationContent(); animation.Name = Path.GetFileNameWithoutExtension(filename); animation.Identity = contentId; bones = new List <BoneInfo>(); reader = new StreamReader(filename); string line; if ((line = readLine()) != "HIERARCHY") { throw new InvalidContentException("no HIERARCHY found", cId); } BoneContent bone = root; while ((line = readLine()) != "MOTION") { if (line == null) { throw new InvalidContentException("premature end of file", cId); } string keyword = line.Split(whiteSpace)[0]; if (keyword == "ROOT" || keyword == "JOINT" || line == "End Site") { BoneInfo boneInfo = new BoneInfo(); BoneContent newBone = new BoneContent(); if (keyword == "JOINT" || line == "End Site") { bone.Children.Add(newBone); } if (keyword == "ROOT") { root = newBone; } if (keyword == "ROOT" || keyword == "JOINT") { newBone.Name = line.Split(whiteSpace)[1]; boneInfo.bone = newBone; bones.Add(boneInfo); } else { newBone.Name = bone.Name + "End"; } if ((line = readLine()) != "{") { throw new InvalidContentException("expected '{' but found " + line, cId); } line = readLine(); if (line != null && line.StartsWith("OFFSET")) { string[] data = line.Split(whiteSpace); //couldn't get the .NET 2.0 version of Split() working, //therefore this ugly hack List <string> coords = new List <string>(); foreach (string s in data) { if (s != "OFFSET" && s != "" && s != null) { coords.Add(s); } } Vector3 v = new Vector3(); v.X = ParseFloat(coords[0]); v.Y = ParseFloat(coords[1]); v.Z = ParseFloat(coords[2]); Matrix offset = Matrix.CreateTranslation(v); newBone.Transform = offset; } else { throw new InvalidContentException("expected 'OFFSET' but found " + line, cId); } if (keyword == "ROOT" || keyword == "JOINT") { line = readLine(); if (line != null && line.StartsWith("CHANNELS")) { string[] channels = line.Split(whiteSpace); for (int i = 2; i < channels.Length; i++) { if (channels[i] != null && channels[i] != "") { boneInfo.Add(channels[i]); } } } else { throw new InvalidContentException("expected 'CHANNELS' but found " + line, cId); } } bone = newBone; } if (line == "}") { bone = (BoneContent)bone.Parent; } } if ((line = readLine()) != null) { string[] data = line.Split(':'); if (data[0] == "Frames") { frames = int.Parse(data[1].Trim()); } } if ((line = readLine()) != null) { string[] data = line.Split(':'); if (data[0] == "Frame Time") { frameTime = double.Parse(data[1].Trim(), CultureInfo.InvariantCulture); } } animation.Duration = TimeSpan.FromSeconds(frameTime * frames); root.Animations.Add(animation.Name, animation); foreach (BoneInfo b in bones) { animation.Channels.Add(b.bone.Name, new AnimationChannel()); } int frameNumber = 0; while ((line = readLine()) != null) { string[] ss = line.Split(whiteSpace); //couldn't get the .NET 2.0 version of Split() working, //therefore this ugly hack List <string> data = new List <string>(); foreach (string s in ss) { if (s != "" && s != null) { data.Add(s); } } int i = 0; foreach (BoneInfo b in bones) { foreach (string channel in b.channels) { b.channelValues[channel] = ParseFloat(data[i]); ++i; } } foreach (BoneInfo b in bones) { // Many applications export BVH in such a way that bone translation // needs to be aplied in every frame. Matrix translation = b.bone.Transform; Vector3 t = new Vector3(); t.X = b["Xposition"]; t.Y = b["Yposition"]; t.Z = b["Zposition"]; if (t.Length() != 0.0f) { // Some applications export BVH with translation channels for every bone. // In this case, bone translation should not be applied. translation = Matrix.CreateTranslation(t); } Quaternion r = Quaternion.Identity; // get rotations in correct order foreach (string channel in b.channels) { float angle = MathHelper.ToRadians(b[channel]); if (channel.Equals("Xrotation", StringComparison.InvariantCultureIgnoreCase)) { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitX, angle); } if (channel.Equals("Yrotation", StringComparison.InvariantCultureIgnoreCase)) { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitY, angle); } if (channel.Equals("Zrotation", StringComparison.InvariantCultureIgnoreCase)) { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitZ, angle); } } Matrix m = Matrix.CreateFromQuaternion(r) * translation; TimeSpan time = TimeSpan.FromSeconds(frameTime * frameNumber); AnimationKeyframe keyframe = new AnimationKeyframe(time, m); animation.Channels[b.bone.Name].Add(keyframe); ++i; } ++frameNumber; } root.Identity = contentId; return(root); }
private static void SortFrames(ref AnimationKeyframe[] frames) { Array.Sort<AnimationKeyframe>(frames, new Comparison<AnimationKeyframe>( delegate(AnimationKeyframe one, AnimationKeyframe two) { return one.Time.CompareTo(two.Time); })); }
/// <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); } } } }
private static void InitializeFrames(ref AnimationKeyframe[] frames) { SortFrames(ref frames); if (frames[0].Time != TimeSpan.Zero) { AnimationKeyframe[] newFrames = new AnimationKeyframe[frames.Length + 1]; Array.ConstrainedCopy(frames, 0, newFrames, 1, frames.Length); newFrames[0] = frames[0]; frames = newFrames; } }
// Interpolates a set of animation key frames to align with // a set of times and copies it into the destination array. private static void InterpFrames( ref AnimationKeyframe[] source, ref Matrix[] dest, IList<TimeSpan> times) { // The index of hte source frame int sourceIndex = 0; for (int i = 0; i < times.Count; i++) { // Increment the index till the next index is greater than the current time while (sourceIndex != source.Length-1 && source[sourceIndex + 1].Time < times[i]) { sourceIndex++; } // If we are at the last index use the last transform for the rest of the times if (sourceIndex==source.Length-1) { dest[i] = source[sourceIndex].Transform; continue; } // If the keyframe time is equal to the current time use the keyframe transform if (source[sourceIndex].Time == times[i]) { dest[i] = source[sourceIndex].Transform; } else // else interpolate { double interpAmount = ((double)times[i].Ticks - source[sourceIndex].Time.Ticks) / ((double)source[sourceIndex + 1].Time.Ticks - source[sourceIndex].Time.Ticks); Matrix m1 = source[sourceIndex].Transform; Matrix m2 = source[sourceIndex + 1].Transform; if (m1 == m2) dest[i] = m1; else dest[i] = Matrix.Lerp(m1, m2, (float)interpAmount); } } }
/// <summary> /// Merges scale, translation, and rotation keyframes into matrix keyframes. /// </summary> /// <param name="scale">The scale keyframes.</param> /// <param name="translation">The translation keyframes.</param> /// <param name="rotation">The rotation keyframes.</param> /// <returns>The merged matrix keyframes.</returns> public static List<AnimationKeyframe> MergeKeyFrames(AnimationKeyframe[] scale, AnimationKeyframe[] translation, AnimationKeyframe[] rotation) { if (scale == null) { scale = new AnimationKeyframe[] {new AnimationKeyframe(new TimeSpan(0), Matrix.Identity)}; } if (translation == null) throw new Exception("Animation data is not stored as matrices and " + "has no translation component."); if (rotation == null) throw new Exception("Animation data is not stored as matrices and " + "has no rotation component"); // Sort the frames by time and make sure they start at time 0 and // have length >= 1 InitializeFrames(ref scale); InitializeFrames(ref translation); InitializeFrames(ref rotation); // Get a sorted list of the timespans for all 3 keyframe types, // not counting duplicates SortedList<TimeSpan, object> keyframeTimes = new SortedList<TimeSpan, object>(); foreach (AnimationKeyframe frame in scale) if (!keyframeTimes.ContainsKey(frame.Time)) keyframeTimes.Add(frame.Time, null); foreach (AnimationKeyframe frame in translation) if (!keyframeTimes.ContainsKey(frame.Time)) keyframeTimes.Add(frame.Time, null); foreach (AnimationKeyframe frame in rotation) if (!keyframeTimes.ContainsKey(frame.Time)) keyframeTimes.Add(frame.Time, null); IList<TimeSpan> times = keyframeTimes.Keys; // Allocate the interpolated frame matrices Matrix[] newScales = new Matrix[keyframeTimes.Count]; Matrix[] newTrans = new Matrix[keyframeTimes.Count]; Matrix[] newRot = new Matrix[keyframeTimes.Count]; List<AnimationKeyframe> returnFrames = new List<AnimationKeyframe>(); // Interpolate the frames based on the times InterpFrames(ref scale, ref newScales, times); InterpFrames(ref translation, ref newTrans, times); InterpFrames(ref rotation, ref newRot, times); // Merge the 3 keyframe types into one. for (int i = 0; i < times.Count; i++) { Matrix m = newRot[i]; m = m * newTrans[i]; m = newScales[i] * m; returnFrames.Add(new AnimationKeyframe(times[i], m)); } return returnFrames; }
/// <summary> /// Imports a keyframe /// </summary> private AnimationKeyframe importKeyframe(string[] s, int frameNumber) { AnimationKeyframe keyframe = null; if (!bones.ContainsKey(s[0])) { throw new InvalidContentException("skeleton does not have bone " + s[0], cId); } BoneContent bone = bones[s[0]]; if (bone.OpaqueData.ContainsKey("dof")) { string[] dof = (string[])bone.OpaqueData["dof"]; int dataLength = s.Length - 1; if (dataLength != dof.Length) { throw new InvalidContentException("AFS DOF specifies " + dof.Length + " values but AMC has " + dataLength, cId); } Matrix transform = Matrix.Identity; Vector3 t = new Vector3(); Quaternion r = Quaternion.Identity; for (int i = 0; i < dataLength; i++) { float data = float.Parse(s[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat); if (dof[i] == "tx") { t.X = data; } else if (dof[i] == "ty") { t.Y = data; } else if (dof[i] == "tz") { t.Z = data; } else if (dof[i] == "rx") { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.ToRadians(data)); } else if (dof[i] == "ry") { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitY, MathHelper.ToRadians(data)); } else if (dof[i] == "rz") { r = r * Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.ToRadians(data)); } } if (t.Length() == 0) { t = bone.Transform.Translation; } transform = Matrix.CreateFromQuaternion(r) * Matrix.CreateTranslation(t); TimeSpan time = TimeSpan.FromTicks(frameNumber * ticksPerFrame); keyframe = new AnimationKeyframe(time, transform); } return(keyframe); }
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> /// Imports Acclaim AMC (motion capture data). /// For a foo_bar.amc, expects skeleton in a file named foo.asf. /// Returns a skeleton with Animations in root bone. /// </summary> public override BoneContent Import(string filename, ContentImporterContext context) { this.context = context; contentId = new ContentIdentity(filename, GetType().ToString()); reader = new StreamReader(filename); AnimationContent animation = new AnimationContent(); animation.Name = Path.GetFileNameWithoutExtension(filename); animation.Identity = contentId; string dir = Path.GetDirectoryName(filename); string asfFilename = animation.Name + ".asf"; if (animation.Name.Contains("_")) { //asfFilename = animation.Name.Split('_')[0] + ".asf"; } asfFilename = dir + @"\" + asfFilename; context.Logger.LogWarning("", contentId, "using skeleton from {0}", asfFilename); AsfImporter asfImporter = new AsfImporter(); BoneContent root = asfImporter.Import(asfFilename, context); bones = asfImporter.Bones; int frameNumber = 1; int maxFrameNumber = 0; string line; animation.Channels.Add("root", new AnimationChannel()); while ((line = readLine()) != null) { if (line[0] != '#' && line[0] != ':') { int fn = 0; if (int.TryParse(line, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out fn)) { ++frameNumber;// = int.Parse(line, NumberStyles.Int, CultureInfo.InvariantCulture.NumberFormat); maxFrameNumber = Math.Max(frameNumber, maxFrameNumber); TimeSpan time = TimeSpan.FromTicks(frameNumber * ticksPerFrame); animation.Channels["root"].Add(new AnimationKeyframe(time, Matrix.Identity)); } else { string[] s = line.Split(' '); string bone = s[0]; if (!animation.Channels.ContainsKey(bone)) { animation.Channels.Add(bone, new AnimationChannel()); } AnimationChannel channel = animation.Channels[bone]; AnimationKeyframe keyframe = importKeyframe(s, frameNumber); if (keyframe != null) { channel.Add(keyframe); } } } } animation.Duration = TimeSpan.FromTicks(maxFrameNumber * ticksPerFrame); context.Logger.LogImportantMessage("imported {0} animation frames for {1} bones", maxFrameNumber, animation.Channels.Count); root.Animations.Add(animation.Name, animation); return(root); }
/// <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> /// Interpolates an AnimationContent object to 60 fps. /// </summary> /// <param name="input">The AnimationContent to interpolate.</param> /// <returns>The interpolated AnimationContent.</returns> public virtual AnimationContent Interpolate(AnimationContent input) { AnimationContent output = new AnimationContent(); long time = 0; long animationDuration = input.Duration.Ticks; // default XNA importers, due to floating point errors or TimeSpan // estimation, sometimes have channels with a duration slightly longer than // the animation duration. So, set the animation duration to its true // value foreach (KeyValuePair <string, AnimationChannel> c in input.Channels) { if (c.Value[c.Value.Count - 1].Time.Ticks > animationDuration) { animationDuration = c.Value[c.Value.Count - 1].Time.Ticks; } } foreach (KeyValuePair <string, AnimationChannel> c in input.Channels) { time = 0; string channelName = c.Key; AnimationChannel channel = c.Value; AnimationChannel outChannel = new AnimationChannel(); int currentFrame = 0; // Step through time until the time passes the animation duration while (time <= animationDuration) { AnimationKeyframe keyframe; // Clamp the time to the duration of the animation and make this // keyframe equal to the last animation frame. if (time >= animationDuration) { time = animationDuration; keyframe = new AnimationKeyframe(new TimeSpan(time), channel[channel.Count - 1].Transform); } else { // If the channel only has one keyframe, set the transform for the current time // to that keyframes transform if (channel.Count == 1 || time < channel[0].Time.Ticks) { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[0].Transform); } // If the current track duration is less than the animation duration, // use the last transform in the track once the time surpasses the duration else if (channel[channel.Count - 1].Time.Ticks <= time) { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[channel.Count - 1].Transform); } else // proceed as normal { // Go to the next frame that is less than the current time while (channel[currentFrame + 1].Time.Ticks < time) { currentFrame++; } // Numerator of the interpolation factor double interpNumerator = (double)(time - channel[currentFrame].Time.Ticks); // Denominator of the interpolation factor double interpDenom = (double)(channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks); // The interpolation factor, or amount to interpolate between the current // and next frame double interpAmount = interpNumerator / interpDenom; // If the frames are roughly 60 frames per second apart, use linear interpolation if (channel[currentFrame + 1].Time.Ticks - channel[currentFrame].Time.Ticks <= ContentUtil.TICKS_PER_60FPS * 1.05) { keyframe = new AnimationKeyframe(new TimeSpan(time), Matrix.Lerp( channel[currentFrame].Transform, channel[currentFrame + 1].Transform, (float)interpAmount)); } else // else if the transforms between the current frame and the next aren't identical // decompose the matrix and interpolate the rotation separately if (channel[currentFrame].Transform != channel[currentFrame + 1].Transform) { keyframe = new AnimationKeyframe(new TimeSpan(time), ContentUtil.SlerpMatrix( channel[currentFrame].Transform, channel[currentFrame + 1].Transform, (float)interpAmount)); } else // Else the adjacent frames have identical transforms and we can use // the current frames transform for the current keyframe. { keyframe = new AnimationKeyframe(new TimeSpan(time), channel[currentFrame].Transform); } } } // Add the interpolated keyframe to the new channel. outChannel.Add(keyframe); // Step the time forward by 1/60th of a second time += ContentUtil.TICKS_PER_60FPS; } // Compensate for the time error,(animation duration % TICKS_PER_60FPS), // caused by the interpolation by setting the last keyframe in the // channel to the animation duration. if (outChannel[outChannel.Count - 1].Time.Ticks < animationDuration) { outChannel.Add(new AnimationKeyframe( TimeSpan.FromTicks(animationDuration), channel[channel.Count - 1].Transform)); } outChannel.Add(new AnimationKeyframe(input.Duration, channel[channel.Count - 1].Transform)); // Add the interpolated channel to the animation output.Channels.Add(channelName, outChannel); } // Set the interpolated duration to equal the inputs duration for consistency output.Duration = TimeSpan.FromTicks(animationDuration); return(output); }
private static AnimationChannel BuildAnimtionChannel(Node animatedNode, NodeAnimationChannel nac) { AnimationChannel newChan = new AnimationChannel(); Matrix animatedNodeTransform = Matrix.Transpose(ToXna(animatedNode.Transform)); Vector3 xnaScale; Microsoft.Xna.Framework.Quaternion xnaRotation; Vector3 xnaPositon; animatedNodeTransform.Decompose(out xnaScale, out xnaRotation, out xnaPositon); Vector3D scale = new Vector3D(xnaScale.X, xnaScale.Y, xnaScale.Z); Assimp.Quaternion rotation; rotation.W = xnaRotation.W; rotation.X = xnaRotation.X; rotation.Y = xnaRotation.Y; rotation.Z = xnaRotation.Z; Vector3D position = new Vector3D(xnaPositon.X, xnaPositon.Y, xnaPositon.Z); int sKeyIndex = 0; int qKeyIndex = 0; int tKeyIndex = 0; double firstSKeyTime = nac.ScalingKeyCount > sKeyIndex ? nac.ScalingKeys[sKeyIndex].Time : Double.MaxValue; double firstQKeyTime = nac.RotationKeyCount > qKeyIndex ? nac.RotationKeys[qKeyIndex].Time : Double.MaxValue; double firstTKeyTime = nac.PositionKeyCount > tKeyIndex ? nac.PositionKeys[tKeyIndex].Time : Double.MaxValue; double currTime = Math.Min(Math.Min(firstSKeyTime, firstQKeyTime), firstTKeyTime); if (firstSKeyTime <= currTime) { scale = nac.ScalingKeys[sKeyIndex].Value; } if (firstQKeyTime <= currTime) { rotation = nac.RotationKeys[qKeyIndex].Value; } if (firstTKeyTime <= currTime) { position = nac.PositionKeys[tKeyIndex].Value; } while (currTime < double.MaxValue) { while (nac.ScalingKeyCount > sKeyIndex + 1 && nac.ScalingKeys[sKeyIndex + 1].Time <= currTime) { sKeyIndex++; scale = nac.ScalingKeys[sKeyIndex].Value; } while (nac.RotationKeyCount > qKeyIndex + 1 && nac.RotationKeys[qKeyIndex + 1].Time <= currTime) { qKeyIndex++; rotation = nac.RotationKeys[qKeyIndex].Value; } while (nac.PositionKeyCount > tKeyIndex + 1 && nac.PositionKeys[tKeyIndex + 1].Time <= currTime) { tKeyIndex++; position = nac.PositionKeys[tKeyIndex].Value; } xnaRotation.W = rotation.W; xnaRotation.X = rotation.X; xnaRotation.Y = rotation.Y; xnaRotation.Z = rotation.Z; Matrix transform = Matrix.CreateScale(ToXna(scale)) * Matrix.CreateFromQuaternion(xnaRotation) * Matrix.CreateTranslation(ToXna(position)); AnimationKeyframe newKeyframe = new AnimationKeyframe(TimeSpan.FromSeconds(currTime), transform); newChan.Add(newKeyframe); // Increment the time: double nextSKeyTime = nac.ScalingKeyCount > sKeyIndex + 1 ? nac.ScalingKeys[sKeyIndex + 1].Time : Double.MaxValue; double nextQKeyTime = nac.RotationKeyCount > qKeyIndex + 1 ? nac.RotationKeys[qKeyIndex + 1].Time : Double.MaxValue; double nextTKeyTime = nac.PositionKeyCount > tKeyIndex + 1 ? nac.PositionKeys[tKeyIndex + 1].Time : Double.MaxValue; currTime = Math.Min(Math.Min(nextSKeyTime, nextQKeyTime), nextTKeyTime); } return(newChan); }