/// <summary> /// Linear interpolation of two keyframe transformations. /// </summary> /// <param name="frame1">One keyframe</param> /// <param name="frame2">Another keyframe</param> /// <param name="amount">Weight of second keyframe between 0.0 and 1.0</param> /// <returns>Interpolated transformation matrix</returns> public static Matrix LerpTransform(JointAnimationKeyFrame frame1, JointAnimationKeyFrame frame2, float amount) { // Lerp between components Vector3 scale = Vector3.Lerp(frame1.Scale, frame2.Scale, amount); Quaternion rotation = Quaternion.Lerp(frame1.Rotation, frame2.Rotation, amount); Vector3 translation = Vector3.Lerp(frame1.Translation, frame2.Translation, amount); return(Matrix.CreateScale(scale) * Matrix.CreateFromQuaternion(rotation) * Matrix.CreateTranslation(translation)); }
/// <summary> /// Linear interpolation of two keyframes. The resulting keyframe will consist /// of the interpolated time and transformation. /// </summary> /// <param name="frame1">One keyframe</param> /// <param name="frame2">Another keyframe</param> /// <param name="amount">Weight of the second keyframe between 0.0 and 1.0</param> /// <returns>Interpolated keyframe</returns> public static JointAnimationKeyFrame Lerp(JointAnimationKeyFrame frame1, JointAnimationKeyFrame frame2, float amount) { // Lerp between components Vector3 scale = Vector3.Lerp(frame1.Scale, frame2.Scale, amount); Quaternion rotation = Quaternion.Lerp(frame1.Rotation, frame2.Rotation, amount); Vector3 translation = Vector3.Lerp(frame1.Translation, frame2.Translation, amount); // Lerp time float time = frame1.Time + (frame2.Time - frame1.Time) * amount; return(new JointAnimationKeyFrame(time, scale, rotation, translation)); }
/// <summary> /// Calculates a key frame at given time by interpolating between the samples /// that are nearest (by time). /// </summary> /// <param name="time">Time</param> /// <returns>Interpolated key frame for given time key</returns> public JointAnimationKeyFrame GetInterpolatedKeyframe(float time) { if (time < StartTime) { switch (PreBehaviour) { case AnimationBehaviour.Constant: time = StartTime; break; case AnimationBehaviour.Cycle: time = 0; break; default: // TODO: Implement other animation behaviours throw new NotImplementedException("No animation behaviours " + "other than Constant implemented yet"); } } else if (time > EndTime) { switch (PostBehaviour) { case AnimationBehaviour.Constant: time = EndTime; break; case AnimationBehaviour.Cycle: time %= EndTime; break; default: // TODO: Implement other animation behaviours throw new NotImplementedException("No animation behaviours " + "other than Constant implemented yet"); } } int keyframeIndex = 0; // Find the two frames to interpolate between while (_keyframes[keyframeIndex].Time <= time) { keyframeIndex++; } if (keyframeIndex > 0) { keyframeIndex--; } JointAnimationKeyFrame frame1 = _keyframes[keyframeIndex]; JointAnimationKeyFrame frame2 = _keyframes[keyframeIndex + 1]; if (frame1.Time == time) { return(frame1); } switch (_interpolation) { case AnimationInterpolation.Linear: float amount = 1.0f / (frame2.Time - frame1.Time) * (time - frame1.Time); return(JointAnimationKeyFrame.Lerp(frame1, frame2, amount)); default: // TODO: Implement other animation interpolations throw new NotImplementedException("No animation interpolation " + "other than Linear implemented yet"); } }
/// <summary> /// Takes a group of animations which affect the same joint and combines them into /// one animation with one channel. For this all transformations of each individual /// keyframe are combined. Translations are added, Scales multiplied and rotations /// multiplied as well. /// </summary> /// <param name="animations">List of animations</param> /// <returns>Combined animation</returns> static JointAnimation CombineAnimations(IEnumerable<JointAnimation> animations) { if (animations.Count() == 1) { // If there is only one animation there's no need to join return animations.First(); } // Number of frames that have to be combined int numFrames = animations.First().NumFrames; // All animations must have the same number of frames if(!animations.All(a => a.NumFrames == numFrames)) { throw new NotImplementedException("Animations affecting the same joint must " + "have the same number of keyframes"); } var combinedKeyframes = new JointAnimationKeyFrame[numFrames]; for (int i = 0; i < numFrames; i++) { // Create a combined key frame float time = animations.First().Channels[0].Sampler.Keyframes[i].Time; Vector3 scale = new Vector3(1,1,1); Quaternion rotation = Quaternion.Identity; Vector3 translation = new Vector3(0,0,0); foreach (var add in animations.Select(anim => anim.Channels[0].Sampler.Keyframes[i])) { if (add.Scale != Vector3.One) scale *= add.Scale; if (add.Translation != Vector3.Zero) translation += add.Translation; // Single axis rotations are executed in order (as defined in the document) // Note: Not sure if this is correct if (add.Rotation != Quaternion.Identity) rotation = add.Rotation * rotation; } var keyframe = new JointAnimationKeyFrame(time, scale, rotation, translation); combinedKeyframes[i] = keyframe; } Joint target = animations.First().Channels[0].Target; var sampler = new JointAnimationSampler(combinedKeyframes, AnimationInterpolation.Linear); JointAnimation animation = new JointAnimation(new JointAnimationChannel[] { new JointAnimationChannel(sampler, target) }); // Names if (animations.Any(a => !String.IsNullOrEmpty(a.Name))) { animation.Name = animations.Where(a => !String.IsNullOrEmpty(a.Name)). Select(a => a.Name).Aggregate((sum, name) => sum + "+" + name); } if (animations.Any(a => !String.IsNullOrEmpty(a.GlobalID))) { animation.GlobalID = animations.Where(a => !String.IsNullOrEmpty(a.GlobalID)). Select(a => a.GlobalID).Aggregate((sum, name) => sum + "\n" + name); } return animation; }
/// <summary> /// Imports an animation sampler. /// Right now only LINEAR interpolation is supported, thus any other definitions /// are simply ignored and LINEAR is used instead. /// </summary> /// <param name="xmlAnimation">XML animation node</param> /// <param name="xmlSampler">XML sampler node</param> /// <param name="target">Value of sampler's target attribute</param> /// <returns>Animation Sampler</returns> // TODO: Implement import of other interpolation methods than LINEAR static JointAnimationSampler ImportSampler(XmlNode xmlAnimation, XmlNode xmlSampler, string target) { // Input and Output sources Source input = Source.FromInput(xmlSampler.SelectSingleNode("input[@semantic='INPUT']"), xmlAnimation); Source output = Source.FromInput(xmlSampler.SelectSingleNode("input[@semantic='OUTPUT']"), xmlAnimation); // Target (matrix, translation.*, rotation.*, scale.*) target = target.Substring(target.IndexOf('/')+1); // It is assumed that TIME is used for input var times = input.GetData<float>(); var keyframes = new JointAnimationKeyFrame[times.Count]; if (target == "matrix") { // Baked matrices were used so that there is one animation per joint animating // the whole transform matrix. Now we just need to save these transforms in keyframes var transforms = output.GetData<Matrix>(); Debug.Assert(transforms.Count == times.Count); for (int i = 0; i < times.Count; i++) { keyframes[i] = new JointAnimationKeyFrame(times[i], transforms[i]); } } else if (output.DataType == typeof(float)) { // No baked matrices, i.e. there is an animation for each component of the transform var data = output.GetData<float>(); Debug.Assert(times.Count == data.Count); for (int i = 0; i < times.Count; i++) { keyframes[i] = CreateKeyFrameFromSingle(target, data[i], times[i]); } } else if (output.DataType == typeof(Vector3)) { var data = output.GetData<Vector3>(); Debug.Assert(times.Count == data.Count); for (int i = 0; i < times.Count; i++) { keyframes[i] = CreateKeyFrameFromVector(target, data[i], times[i]); } } return new JointAnimationSampler(keyframes, AnimationInterpolation.Linear); }
/// <summary> /// Creates a new animation sampler from a list of keyframes /// </summary> /// <param name="keyframes">List of samples</param> /// <param name="interpolation">Interpolation method to use</param> public JointAnimationSampler(JointAnimationKeyFrame[] keyframes, AnimationInterpolation interpolation) { _keyframes = keyframes; _interpolation = interpolation; if (_interpolation != AnimationInterpolation.Linear) { // Right now only linear interpolation is supported _interpolation = AnimationInterpolation.Linear; } if (keyframes == null || keyframes.Length == 0) { throw new ApplicationException("Sampler needs keyframes"); } StartTime = keyframes[0].Time; EndTime = keyframes[keyframes.Length - 1].Time; PostBehaviour = AnimationBehaviour.Cycle; }
/// <summary> /// Linear interpolation of two keyframe transformations. /// </summary> /// <param name="frame1">One keyframe</param> /// <param name="frame2">Another keyframe</param> /// <param name="amount">Weight of second keyframe between 0.0 and 1.0</param> /// <returns>Interpolated transformation matrix</returns> public static Matrix LerpTransform(JointAnimationKeyFrame frame1, JointAnimationKeyFrame frame2, float amount) { // Lerp between components Vector3 scale = Vector3.Lerp(frame1.Scale, frame2.Scale, amount); Quaternion rotation = Quaternion.Lerp(frame1.Rotation, frame2.Rotation, amount); Vector3 translation = Vector3.Lerp(frame1.Translation, frame2.Translation, amount); return Matrix.CreateScale(scale) * Matrix.CreateFromQuaternion(rotation) * Matrix.CreateTranslation(translation); }
/// <summary> /// Linear interpolation of two keyframes. The resulting keyframe will consist /// of the interpolated time and transformation. /// </summary> /// <param name="frame1">One keyframe</param> /// <param name="frame2">Another keyframe</param> /// <param name="amount">Weight of the second keyframe between 0.0 and 1.0</param> /// <returns>Interpolated keyframe</returns> public static JointAnimationKeyFrame Lerp(JointAnimationKeyFrame frame1, JointAnimationKeyFrame frame2, float amount) { // Lerp between components Vector3 scale = Vector3.Lerp(frame1.Scale, frame2.Scale, amount); Quaternion rotation = Quaternion.Lerp(frame1.Rotation, frame2.Rotation, amount); Vector3 translation = Vector3.Lerp(frame1.Translation, frame2.Translation, amount); // Lerp time float time = frame1.Time + (frame2.Time - frame1.Time) * amount; return new JointAnimationKeyFrame(time, scale, rotation, translation); }