private AnimationContent CreateAnimation(Assimp.Animation aiAnimation) { var animation = new AnimationContent { Name = FixupAnimationName(aiAnimation.Name), Duration = TimeSpan.FromSeconds(aiAnimation.DurationInTicks / aiAnimation.TicksPerSecond) }; foreach (var aiChannel in aiAnimation.NodeAnimationChannels) { var channel = new AnimationChannel(); // We can have different numbers of keyframes for each, so find the max index. var keyCount = Math.Max(aiChannel.PositionKeyCount, Math.Max(aiChannel.RotationKeyCount, aiChannel.ScalingKeyCount)); // Get all unique keyframe times var times = aiChannel.PositionKeys.Select(k => k.Time) .Union(aiChannel.RotationKeys.Select(k => k.Time)) .Union(aiChannel.ScalingKeys.Select(k => k.Time)) .Distinct().ToList(); // The rest of this loop is almost certainly wrong. Don't trust it. // There's some magical combination, ordering, or transposition we have // to figure out to translate FBX->Assimp->XNA. // Possibilities: matrix offset transform, missing a base transform, an extra base transform, etc. var toBoneSpace = _objectToBone.ContainsKey(aiChannel.NodeName) ? _objectToBone[aiChannel.NodeName] * _skeletonRoot.Transform : _skeletonRoot.Transform; foreach (var aiKeyTime in times) { var time = TimeSpan.FromSeconds(aiKeyTime / aiAnimation.TicksPerSecond); var translation = Matrix4x4.FromTranslation(aiChannel.PositionKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value); var rotation = new Matrix4x4(aiChannel.RotationKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value.GetMatrix()); var scale = Matrix4x4.FromScaling(aiChannel.ScalingKeys.FirstOrDefault(k => k.Time == aiKeyTime).Value); var nodeTransform = translation * rotation * scale; var xform = toBoneSpace * nodeTransform * _globalInverseXform; channel.Add(new AnimationKeyframe(time, ToXna(xform))); } animation.Channels.Add(aiChannel.NodeName, channel); } return(animation); }
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> /// 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); }
/* * template Animation * { * [...] * } */ /// <summary> /// Fills in all the channels of an animation. Each channel refers to /// a single bone's role in the animation. /// </summary> /// <param name="boneName">The name of the bone associated with the channel</param> /// <returns>The imported animation channel</returns> private AnimationChannel ImportAnimationChannel(out string boneName) { AnimationChannel anim = new AnimationChannel(); // Store the frames in an array, which acts as an intermediate data set // This will allow us to more easily provide support for non Matrix // animation keys at a later time AnimationKeyframe[] rotFrames = null; AnimationKeyframe[] transFrames = null; AnimationKeyframe[] scaleFrames = null; List <AnimationKeyframe> matrixFrames = null; boneName = null; tokens.SkipName(); for (string next = tokens.NextToken(); next != "}"; next = tokens.NextToken()) { // A set of animation keys if (next == "AnimationKey") { // These keys can be rotation (0),scale(1),translation(2), or matrix(3 or 4) keys. int keyType; AnimationKeyframe[] frames = ImportAnimationKey(out keyType); if (keyType == 0) { rotFrames = frames; } else if (keyType == 1) { scaleFrames = frames; } else if (keyType == 2) { transFrames = frames; } else { matrixFrames = new List <AnimationKeyframe>(frames); } } // A possible bone name else if (next == "{") { string token = tokens.NextToken(); if (tokens.NextToken() != "}") { tokens.SkipNode(); } else { boneName = token; } } } // Fill in the channel with the frames if (matrixFrames != null) { matrixFrames.Sort(new Comparison <AnimationKeyframe>(delegate(AnimationKeyframe one, AnimationKeyframe two) { return(one.Time.CompareTo(two.Time)); })); if (matrixFrames[0].Time != TimeSpan.Zero) { matrixFrames.Insert(0, new AnimationKeyframe(new TimeSpan(), matrixFrames[0].Transform)); } for (int i = 0; i < matrixFrames.Count; i++) { Matrix m = matrixFrames[i].Transform; ContentUtil.ReflectMatrix(ref m); matrixFrames[i].Transform = m; anim.Add(matrixFrames[i]); } } else { List <AnimationKeyframe> combinedFrames = ContentUtil.MergeKeyFrames( scaleFrames, transFrames, rotFrames); for (int i = 0; i < combinedFrames.Count; i++) { Matrix m = combinedFrames[i].Transform; ContentUtil.ReflectMatrix(ref m); combinedFrames[i].Transform = m; //* Matrix.CreateRotationX(MathHelper.PiOver2); anim.Add(combinedFrames[i]); } } return(anim); }
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 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); } } } }
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 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 the specified animation to XNA. /// </summary> /// <param name="aiAnimation">The animation.</param> /// <returns>The animation converted to XNA.</returns> private AnimationContent ImportAnimation(Animation aiAnimation) { var animation = new AnimationContent { Name = GetAnimationName(aiAnimation.Name), Identity = _identity, Duration = TimeSpan.FromSeconds(aiAnimation.DurationInTicks / aiAnimation.TicksPerSecond) }; // In Assimp animation channels may be split into separate channels. // "nodeXyz" --> "nodeXyz_$AssimpFbx$_Translation", // "nodeXyz_$AssimpFbx$_Rotation", // "nodeXyz_$AssimpFbx$_Scaling" // Group animation channels by name (strip the "_$AssimpFbx$" part). var channelGroups = aiAnimation.NodeAnimationChannels .GroupBy(channel => GetNodeName(channel.NodeName)); foreach (var channelGroup in channelGroups) { var boneName = channelGroup.Key; var channel = new AnimationChannel(); // Get transformation pivot for current bone. FbxPivot pivot; if (!_pivots.TryGetValue(boneName, out pivot)) { pivot = FbxPivot.Default; } var scaleKeys = EmptyVectorKeys; var rotationKeys = EmptyQuaternionKeys; var translationKeys = EmptyVectorKeys; foreach (var aiChannel in channelGroup) { if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Scaling")) { scaleKeys = aiChannel.ScalingKeys; Debug.Assert(pivot.Scaling.HasValue); Debug.Assert(!aiChannel.HasRotationKeys || (aiChannel.RotationKeyCount == 1 && (aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(1, 0, 0, 0) || aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(0, 0, 0, 0)))); Debug.Assert(!aiChannel.HasPositionKeys || (aiChannel.PositionKeyCount == 1 && aiChannel.PositionKeys[0].Value == new Vector3D(0, 0, 0))); } else if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Rotation")) { rotationKeys = aiChannel.RotationKeys; Debug.Assert(pivot.Rotation.HasValue); Debug.Assert(!aiChannel.HasScalingKeys || (aiChannel.ScalingKeyCount == 1 && aiChannel.ScalingKeys[0].Value == new Vector3D(1, 1, 1))); Debug.Assert(!aiChannel.HasPositionKeys || (aiChannel.PositionKeyCount == 1 && aiChannel.PositionKeys[0].Value == new Vector3D(0, 0, 0))); } else if (aiChannel.NodeName.EndsWith("_$AssimpFbx$_Translation")) { translationKeys = aiChannel.PositionKeys; Debug.Assert(pivot.Translation.HasValue); Debug.Assert(!aiChannel.HasScalingKeys || (aiChannel.ScalingKeyCount == 1 && aiChannel.ScalingKeys[0].Value == new Vector3D(1, 1, 1))); Debug.Assert(!aiChannel.HasRotationKeys || (aiChannel.RotationKeyCount == 1 && (aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(1, 0, 0, 0) || aiChannel.RotationKeys[0].Value == new Assimp.Quaternion(0, 0, 0, 0)))); } else { scaleKeys = aiChannel.ScalingKeys; rotationKeys = aiChannel.RotationKeys; translationKeys = aiChannel.PositionKeys; } } // Get all unique keyframe times. (Assuming that no two key frames // have the same time, which is usually a safe assumption.) var times = scaleKeys.Select(k => k.Time) .Union(rotationKeys.Select(k => k.Time)) .Union(translationKeys.Select(k => k.Time)) .OrderBy(t => t) .ToList(); Debug.Assert(times.Count == times.Distinct().Count(), "Sequences combined with Union() should not have duplicates."); int prevScaleIndex = -1; int prevRotationIndex = -1; int prevTranslationIndex = -1; double prevScaleTime = 0.0; double prevRotationTime = 0.0; double prevTranslationTime = 0.0; Vector3? prevScale = null; Quaternion?prevRotation = null; Vector3? prevTranslation = null; foreach (var time in times) { // Get scaling. Vector3?scale; int scaleIndex = scaleKeys.FindIndex(k => k.Time == time); if (scaleIndex != -1) { // Scaling key found. scale = ToXna(scaleKeys[scaleIndex].Value); prevScaleIndex = scaleIndex; prevScaleTime = time; prevScale = scale; } else { // No scaling key found. if (prevScaleIndex != -1 && prevScaleIndex + 1 < scaleKeys.Count) { // Lerp between previous and next scaling key. var nextScaleKey = scaleKeys[prevScaleIndex + 1]; var nextScaleTime = nextScaleKey.Time; var nextScale = ToXna(nextScaleKey.Value); var amount = (float)((time - prevScaleTime) / (nextScaleTime - prevScaleTime)); scale = Vector3.Lerp(prevScale.Value, nextScale, amount); } else { // Hold previous scaling value. scale = prevScale; } } // Get rotation. Quaternion?rotation; int rotationIndex = rotationKeys.FindIndex(k => k.Time == time); if (rotationIndex != -1) { // Rotation key found. rotation = ToXna(rotationKeys[rotationIndex].Value); prevRotationIndex = rotationIndex; prevRotationTime = time; prevRotation = rotation; } else { // No rotation key found. if (prevRotationIndex != -1 && prevRotationIndex + 1 < rotationKeys.Count) { // Lerp between previous and next rotation key. var nextRotationKey = rotationKeys[prevRotationIndex + 1]; var nextRotationTime = nextRotationKey.Time; var nextRotation = ToXna(nextRotationKey.Value); var amount = (float)((time - prevRotationTime) / (nextRotationTime - prevRotationTime)); rotation = Quaternion.Slerp(prevRotation.Value, nextRotation, amount); } else { // Hold previous rotation value. rotation = prevRotation; } } // Get translation. Vector3?translation; int translationIndex = translationKeys.FindIndex(k => k.Time == time); if (translationIndex != -1) { // Translation key found. translation = ToXna(translationKeys[translationIndex].Value); prevTranslationIndex = translationIndex; prevTranslationTime = time; prevTranslation = translation; } else { // No translation key found. if (prevTranslationIndex != -1 && prevTranslationIndex + 1 < translationKeys.Count) { // Lerp between previous and next translation key. var nextTranslationKey = translationKeys[prevTranslationIndex + 1]; var nextTranslationTime = nextTranslationKey.Time; var nextTranslation = ToXna(nextTranslationKey.Value); var amount = (float)((time - prevTranslationTime) / (nextTranslationTime - prevTranslationTime)); translation = Vector3.Lerp(prevTranslation.Value, nextTranslation, amount); } else { // Hold previous translation value. translation = prevTranslation; } } // Apply transformation pivot. var transform = Matrix.Identity; if (pivot.GeometricScaling.HasValue) { transform *= pivot.GeometricScaling.Value; } if (pivot.GeometricRotation.HasValue) { transform *= pivot.GeometricRotation.Value; } if (pivot.GeometricTranslation.HasValue) { transform *= pivot.GeometricTranslation.Value; } if (pivot.ScalingPivotInverse.HasValue) { transform *= pivot.ScalingPivotInverse.Value; } if (scale.HasValue) { transform *= Matrix.CreateScale(scale.Value); } else if (pivot.Scaling.HasValue) { transform *= pivot.Scaling.Value; } if (pivot.ScalingPivot.HasValue) { transform *= pivot.ScalingPivot.Value; } if (pivot.ScalingOffset.HasValue) { transform *= pivot.ScalingOffset.Value; } if (pivot.RotationPivotInverse.HasValue) { transform *= pivot.RotationPivotInverse.Value; } if (pivot.PostRotation.HasValue) { transform *= pivot.PostRotation.Value; } if (rotation.HasValue) { transform *= Matrix.CreateFromQuaternion(rotation.Value); } else if (pivot.Rotation.HasValue) { transform *= pivot.Rotation.Value; } if (pivot.PreRotation.HasValue) { transform *= pivot.PreRotation.Value; } if (pivot.RotationPivot.HasValue) { transform *= pivot.RotationPivot.Value; } if (pivot.RotationOffset.HasValue) { transform *= pivot.RotationOffset.Value; } if (translation.HasValue) { transform *= Matrix.CreateTranslation(translation.Value); } else if (pivot.Translation.HasValue) { transform *= pivot.Translation.Value; } channel.Add(new AnimationKeyframe(TimeSpan.FromSeconds(time / aiAnimation.TicksPerSecond), transform)); } animation.Channels[channelGroup.Key] = channel; } return(animation); }
/// <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> /// The importer's entry point. /// Called by the framework when importing a game asset. /// </summary> /// <param name="filename">Name of a game asset file.</param> /// <param name="context"> /// Contains information for importing a game asset, such as a logger interface. /// </param> /// <returns>Resulting game asset.</returns> public override NodeContent Import(string filename, ContentImporterContext context) { // Uncomment the following line to debug: //System.Diagnostics.Debugger.Launch(); importerContext = context; // Reset all importer state // See field declarations for more information rootNode = new NodeContent(); meshBuilder = null; this.filename = filename; // Model identity is tied to the file it is loaded from rootNode.Identity = new ContentIdentity(filename); var flags = (ppsteps | aiPostProcessSteps.aiProcess_GenSmoothNormals | // generate smooth normal vectors if not existing aiPostProcessSteps.aiProcess_SplitLargeMeshes | // split large, unrenderable meshes into submeshes aiPostProcessSteps.aiProcess_Triangulate | // triangulate polygons with more than 3 edges aiPostProcessSteps.aiProcess_ConvertToLeftHanded | // convert everything to D3D left handed space aiPostProcessSteps.aiProcess_SortByPType | // make 'clean' meshes which consist of a single typ of primitives aiPostProcessSteps.aiProcess_FixInfacingNormals | aiPostProcessSteps.aiProcess_FlipWindingOrder | (aiPostProcessSteps)0); Importer importer = new Importer(); scene = importer.ReadFile(filename, flags); if (scene != null) { directory = Path.GetDirectoryName(filename); } else { throw new InvalidContentException("Failed to open file: " + filename + ". Either Assimp screwed up or the path is not valid."); } ///animacoes Dictionary <String, AnimationContent> AnimNameToAcontent = new Dictionary <string, AnimationContent>(); for (int i = 0; i < scene.mNumAnimations; i++) { AnimationContent AnimationContent = new AnimationContent(); AnimationContent.Duration = TimeSpan.FromMilliseconds(scene.mAnimations[i].mDuration); if (String.IsNullOrEmpty(scene.mAnimations[i].mName.Data)) { scene.mAnimations[i].mName.Data = "EMPTY"; } AnimationContent.Name = scene.mAnimations[i].mName.Data; AnimNameToAcontent.Add(scene.mAnimations[i].mName.Data, AnimationContent); for (int j = 0; j < scene.mAnimations[i].mNumChannels; j++) { AnimationChannel AnimationChannel; String boneName = scene.mAnimations[i].mChannels[j].mNodeName.Data; if (AnimationContent.Channels.ContainsKey(boneName)) { AnimationChannel = AnimationContent.Channels[boneName]; } else { AnimationChannel = new AnimationChannel(); AnimationContent.Channels.Add(boneName, AnimationChannel); } Dictionary <double, Vector3> position = new Dictionary <double, Vector3>(); Dictionary <double, Vector3> scales = new Dictionary <double, Vector3>(); Dictionary <double, Quaternion> rots = new Dictionary <double, Quaternion>(); SortedSet <double> set = new SortedSet <double>(); for (int w = 0; w < scene.mAnimations[i].mChannels[j].mNumPositionKeys; w++) { position.Add(scene.mAnimations[i].mChannels[j].mPositionKeys[w].mTime, new Vector3(scene.mAnimations[i].mChannels[j].mPositionKeys[w].mValue.x, scene.mAnimations[i].mChannels[j].mPositionKeys[w].mValue.y, scene.mAnimations[i].mChannels[j].mPositionKeys[w].mValue.z)); set.Add(scene.mAnimations[i].mChannels[j].mPositionKeys[w].mTime); } for (int w = 0; w < scene.mAnimations[i].mChannels[j].mNumScalingKeys; w++) { scales.Add(scene.mAnimations[i].mChannels[j].mScalingKeys[w].mTime, new Vector3(scene.mAnimations[i].mChannels[j].mScalingKeys[w].mValue.x, scene.mAnimations[i].mChannels[j].mScalingKeys[w].mValue.y, scene.mAnimations[i].mChannels[j].mScalingKeys[w].mValue.z)); set.Add(scene.mAnimations[i].mChannels[j].mScalingKeys[w].mTime); } for (int w = 0; w < scene.mAnimations[i].mChannels[j].mNumRotationKeys; w++) { rots.Add(scene.mAnimations[i].mChannels[j].mRotationKeys[w].mTime, new Quaternion(scene.mAnimations[i].mChannels[j].mRotationKeys[w].mValue.x, scene.mAnimations[i].mChannels[j].mRotationKeys[w].mValue.y, scene.mAnimations[i].mChannels[j].mRotationKeys[w].mValue.z, scene.mAnimations[i].mChannels[j].mRotationKeys[w].mValue.w)); set.Add(scene.mAnimations[i].mChannels[j].mRotationKeys[w].mTime); } foreach (var item in set) { Vector3 pos; Vector3 sca; Quaternion rot; if (position.TryGetValue(item, out pos) == false) { pos = Vector3.Zero; } if (rots.TryGetValue(item, out rot) == false) { rot = Quaternion.Identity; } if (scales.TryGetValue(item, out sca) == false) { sca = Vector3.One; } Matrix world = Matrix.CreateScale(sca) * Matrix.CreateFromQuaternion(rot) * Matrix.CreateTranslation(pos); AnimationChannel.Add(new AnimationKeyframe(TimeSpan.FromMilliseconds(item), world)); } } } rootNode.Children.Add(extractNo(scene.mRootNode)); aiNode boneRoot = null; aiBone broot = null; int distance = int.MaxValue; ///pega o root manow foreach (var item in bones) { aiNode ainode = scene.mRootNode.FindNode(item.mName); aiNode n = ainode; int d = 0; //find the closest to the root =P while (n.mParent != null) { d++; n = n.mParent; } if (d < distance) { broot = item; boneRoot = ainode; distance = d; } } if (broot != null) { BoneContent BoneContent = new BoneContent(); BoneContent.Name = broot.mName.Data; BoneContent.Transform = tomatrix(broot.mOffsetMatrix); rootNode.Children.Add(BoneContent); foreach (var item2 in AnimNameToAcontent) { BoneContent.Animations.Add(item2.Key, item2.Value); } foreach (var item in boneRoot.mChildren) { BoneContent.Children.Add(createSkel(boneRoot)); } ClearTree(rootNode); } return(rootNode); }
private AnimationContent ImportSkeletalAnimation(H3DModel model, H3DAnimation animation) { var framesCount = (int)animation.FramesCount + 1; var animationNode = new AnimationContent { Name = animation.Name, Identity = _identity, Duration = TimeSpan.FromSeconds((float)1 / (float)30 * (float)framesCount) }; foreach (var elem in animation.Elements) { if (elem.PrimitiveType != H3DPrimitiveType.Transform && elem.PrimitiveType != H3DPrimitiveType.QuatTransform) { continue; } var sklBone = model.Skeleton.FirstOrDefault(x => x.Name == elem.Name); var parent = sklBone != null && sklBone.ParentIndex != -1 ? model.Skeleton[sklBone.ParentIndex] : null; var channel = new AnimationChannel(); for (var frame = 0; frame < framesCount; frame++) { var translation = Matrix.Identity; if (elem.Content is H3DAnimTransform transform0) { if (!transform0.TranslationExists) { continue; } translation = Matrix.CreateTranslation(new Vector3( transform0.TranslationX.Exists ? transform0.TranslationX.GetFrameValue(frame) : sklBone.Translation.X, transform0.TranslationY.Exists ? transform0.TranslationY.GetFrameValue(frame) : sklBone.Translation.Y, transform0.TranslationZ.Exists ? transform0.TranslationZ.GetFrameValue(frame) : sklBone.Translation.Z)); } else if (elem.Content is H3DAnimQuatTransform quatTransform) { if (!quatTransform.HasTranslation) { continue; } translation = Matrix.CreateTranslation(quatTransform.GetTranslationValue(frame).ToXNA()); } var rotation = Matrix.Identity; if (elem.Content is H3DAnimTransform transform1) { if (!(transform1.RotationX.Exists || transform1.RotationY.Exists || transform1.RotationZ.Exists)) { continue; } rotation = Matrix.CreateRotationX(transform1.RotationX.GetFrameValue(frame)) * Matrix.CreateRotationY(transform1.RotationY.GetFrameValue(frame)) * Matrix.CreateRotationZ(transform1.RotationZ.GetFrameValue(frame)); } else if (elem.Content is H3DAnimQuatTransform quatTransform) { if (!quatTransform.HasRotation) { continue; } rotation = Matrix.CreateFromQuaternion(quatTransform.GetRotationValue(frame).ToXNA()); } var scale = Matrix.Identity; var invScale = System.Numerics.Vector3.One; var pElem = animation.Elements.FirstOrDefault(x => x.Name == parent?.Name); if (elem.Content is H3DAnimTransform transform2) { if (!transform2.ScaleExists) { continue; } //Compensate parent bone scale (basically, don't inherit scales) if (parent != null && (sklBone.Flags & H3DBoneFlags.IsSegmentScaleCompensate) != 0) { if (pElem != null) { var pTrans = (H3DAnimTransform)pElem.Content; invScale /= new System.Numerics.Vector3( pTrans.ScaleX.Exists ? pTrans.ScaleX.GetFrameValue(frame) : parent.Scale.X, pTrans.ScaleY.Exists ? pTrans.ScaleY.GetFrameValue(frame) : parent.Scale.Y, pTrans.ScaleZ.Exists ? pTrans.ScaleZ.GetFrameValue(frame) : parent.Scale.Z); } else { invScale /= parent.Scale; } } scale = Matrix.CreateScale((invScale * new System.Numerics.Vector3( transform2.ScaleX.Exists ? transform2.ScaleX.GetFrameValue(frame) : sklBone.Scale.X, transform2.ScaleY.Exists ? transform2.ScaleY.GetFrameValue(frame) : sklBone.Scale.Y, transform2.ScaleZ.Exists ? transform2.ScaleZ.GetFrameValue(frame) : sklBone.Scale.Z)).ToXNA()); } else if (elem.Content is H3DAnimQuatTransform quatTransform) { if (!quatTransform.HasScale) { continue; } //Compensate parent bone scale (basically, don't inherit scales) if (parent != null && (sklBone.Flags & H3DBoneFlags.IsSegmentScaleCompensate) != 0) { if (pElem != null) { invScale /= ((H3DAnimQuatTransform)pElem.Content).GetScaleValue(frame); } else { invScale /= parent.Scale; } } scale = Matrix.CreateScale((invScale * quatTransform.GetScaleValue(frame)).ToXNA()); } channel.Add(new AnimationKeyframe(TimeSpan.FromSeconds((float)frame / 30f), scale * rotation * translation)); } animationNode.Channels[elem.Name] = channel; } return(animationNode); }
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); }