/// <summary> /// Method to determine if this track has any KeyFrames which are /// doing anything useful - can be used to determine if this track /// can be optimised out. /// </summary> public override bool HasNonZeroKeyFrames() { for (int i = 0; i < keyFrameList.Count; i++) { KeyFrame keyFrame = keyFrameList[i]; // look for keyframes which have any component which is non-zero // Since exporters can be a little inaccurate sometimes we use a // tolerance value rather than looking for nothing TransformKeyFrame kf = (TransformKeyFrame)keyFrame; Vector3 trans = kf.Translate; Vector3 scale = kf.Scale; Vector3 axis = Vector3.Zero; float angle = 0f; kf.Rotation.ToAngleAxis(ref angle, ref axis); float tolerance = 1e-3f; if (trans.Length > tolerance || (scale - Vector3.UnitScale).Length > tolerance || !MathUtil.FloatEqual(MathUtil.DegreesToRadians(angle), 0.0f, tolerance)) { return(true); } } return(false); }
/// <summary>Used to rebuild the internal interpolation splines for translations, rotations, and scaling.</summary> protected void BuildInterpolationSplines() { // dont calculate on the fly, wait till the end when we do it manually positionSpline.AutoCalculate = false; rotationSpline.AutoCalculate = false; scaleSpline.AutoCalculate = false; positionSpline.Clear(); rotationSpline.Clear(); scaleSpline.Clear(); // add spline control points for each keyframe in the list for (int i = 0; i < keyFrameList.Count; i++) { TransformKeyFrame keyFrame = (TransformKeyFrame)keyFrameList[i]; positionSpline.AddPoint(keyFrame.Translate); rotationSpline.AddPoint(keyFrame.Rotation); scaleSpline.AddPoint(keyFrame.Scale); } // recalculate all spline tangents now positionSpline.RecalculateTangents(); rotationSpline.RecalculateTangents(); scaleSpline.RecalculateTangents(); isSplineRebuildNeeded = false; }
/// <summary> /// Same as the Apply method, but applies to a specified Node instead of it's associated node. /// </summary> /// <param name="node"></param> /// <param name="time"></param> /// <param name="weight"></param> /// <param name="accumulate"></param> public void ApplyToNode(Node node, float time, float weight, bool accumulate, float scale) { TransformKeyFrame kf = new TransformKeyFrame(null, time); this.GetInterpolatedKeyFrame(time, kf); if (accumulate) { // add to existing. Weights are not relative, but treated as absolute multipliers for the animation Vector3 translate = kf.Translate * weight * scale; node.Translate(translate); // interpolate between not rotation and full rotation, to point weight, so 0 = no rotate, and 1 = full rotation Quaternion rotate = Quaternion.Slerp(weight, Quaternion.Identity, kf.Rotation); node.Rotate(rotate); // TODO: not yet sure how to modify scale for cumulative animations Vector3 scaleVector = kf.Scale; // Not sure how to modify scale for cumulative anims... leave it alone //scaleVector = ((Vector3::UNIT_SCALE - kf.getScale()) * weight) + Vector3::UNIT_SCALE; if (scale != 1.0f && scaleVector != Vector3.UnitScale) { scaleVector = Vector3.UnitScale + (scaleVector - Vector3.UnitScale) * scale; } node.Scale(scaleVector); } else { // apply using weighted transform method node.WeightedTransform(weight, ref kf.translate, ref kf.rotation, ref kf.scale, false); } }
/// <summary> Optimise the current track by removing any duplicate keyframes. </summary> public override void Optimise() { // Eliminate duplicate keyframes from 2nd to penultimate keyframe // NB only eliminate middle keys from sequences of 5+ identical keyframes // since we need to preserve the boundary keys in place, and we need // 2 at each end to preserve tangents for spline interpolation Vector3 lasttrans = Vector3.Zero; Vector3 lastscale = Vector3.Zero; Quaternion lastorientation = Quaternion.Zero; float tolerance = 1e-3f; float quatTolerance = 1e-3f; ushort k = 0; ushort dupKfCount = 0; List <short> removeList = new List <short>(); for (int i = 0; i < keyFrameList.Count; i++) { KeyFrame keyFrame = keyFrameList[i]; TransformKeyFrame kf = (TransformKeyFrame)keyFrame; Vector3 newtrans = kf.Translate; Vector3 newscale = kf.Scale; Quaternion neworientation = kf.Rotation; // Ignore first keyframe; now include the last keyframe as we eliminate // only k-2 in a group of 5 to ensure we only eliminate middle keys if (i != 0 && newtrans.DifferenceLessThan(lasttrans, tolerance) && newscale.DifferenceLessThan(lastscale, tolerance) && neworientation.Equals(lastorientation, quatTolerance)) { ++dupKfCount; // 4 indicates this is the 5th duplicate keyframe if (dupKfCount == 4) { // remove the 'middle' keyframe removeList.Add((short)(k - 2)); --dupKfCount; } } else { // reset dupKfCount = 0; lasttrans = newtrans; lastscale = newscale; lastorientation = neworientation; } } // Now remove keyframes, in reverse order to avoid index revocation for (int i = removeList.Count - 1; i >= 0; i--) { RemoveKeyFrame(removeList[i]); } }
protected XmlElement WriteKeyFrame(TransformKeyFrame keyFrame) { XmlElement node = document.CreateElement("keyframe"); XmlAttribute attr; attr = document.CreateAttribute("time"); attr.Value = keyFrame.Time.ToString(); node.Attributes.Append(attr); XmlElement translate = WriteTranslate(keyFrame.Translate); node.AppendChild(translate); XmlElement rotate = WriteRotate(keyFrame.Rotation); node.AppendChild(rotate); return node; }
protected void WriteKeyFrame( BinaryWriter writer, TransformKeyFrame keyFrame ) { long start_offset = writer.Seek( 0, SeekOrigin.Current ); WriteChunk( writer, SkeletonChunkID.KeyFrame, 0 ); WriteFloat( writer, keyFrame.Time ); WriteQuat( writer, keyFrame.Rotation ); WriteVector3( writer, keyFrame.Translate ); if ( keyFrame.Scale != Vector3.UnitScale ) WriteVector3( writer, keyFrame.Scale ); long end_offset = writer.Seek( 0, SeekOrigin.Current ); writer.Seek( (int)start_offset, SeekOrigin.Begin ); WriteChunk( writer, SkeletonChunkID.KeyFrame, (int)( end_offset - start_offset ) ); writer.Seek( (int)end_offset, SeekOrigin.Begin ); }
/// <summary> /// /// </summary> /// <param name="fileName"></param> public void DumpContents(string fileName) { FileStream fs = File.Open(fileName, FileMode.Create); StreamWriter writer = new StreamWriter(fs); writer.AutoFlush = true; writer.WriteLine("-= Debug output of skeleton {0} =-", this.name); writer.WriteLine(""); writer.WriteLine("== Bones =="); writer.WriteLine("Number of bones: {0}", boneList.Count); Quaternion q = new Quaternion(); float angle = 0; Vector3 axis = new Vector3(); // write each bone out foreach (Bone bone in boneList.Values) { writer.WriteLine("-- Bone {0} --", bone.Handle); writer.Write("Position: {0}", bone.Position); q = bone.Orientation; writer.Write("Rotation: {0}", q); q.ToAngleAxis(ref angle, ref axis); writer.Write(" = {0} radians around axis {1}", angle, axis); writer.WriteLine(""); writer.WriteLine(""); } writer.WriteLine("== Animations =="); writer.WriteLine("Number of animations: {0}", animationList.Count); // animations foreach (Animation anim in animationList) { writer.WriteLine("-- Animation '{0}' (length {1}) --", anim.Name, anim.Length); writer.WriteLine("Number of tracks: {0}", anim.NodeTracks.Count); // tracks foreach (NodeAnimationTrack track in anim.NodeTracks.Values) { writer.WriteLine(" -- AnimationTrack {0} --", track.Handle); writer.WriteLine(" Affects bone: {0}", ((Bone)track.TargetNode).Handle); writer.WriteLine(" Number of keyframes: {0}", track.KeyFrames.Count); // key frames int kf = 0; for (ushort i = 0; i < track.KeyFrames.Count; i++) { TransformKeyFrame keyFrame = track.GetNodeKeyFrame(i); writer.WriteLine(" -- KeyFrame {0} --", kf++); writer.Write(" Time index: {0}", keyFrame.Time); writer.WriteLine(" Translation: {0}", keyFrame.Translate); q = keyFrame.Rotation; writer.Write(" Rotation: {0}", q); q.ToAngleAxis(ref angle, ref axis); writer.WriteLine(" = {0} radians around axis {1}", angle, axis); } } } writer.Close(); fs.Close(); }
/// <summary> /// Same as the Apply method, but applies to a specified Node instead of it's associated node. /// </summary> /// <param name="node"></param> /// <param name="time"></param> /// <param name="weight"></param> /// <param name="accumulate"></param> public void ApplyToNode(Node node, float time, float weight, bool accumulate, float scale) { TransformKeyFrame kf = new TransformKeyFrame(null, time); this.GetInterpolatedKeyFrame(time, kf); if(accumulate) { // add to existing. Weights are not relative, but treated as absolute multipliers for the animation Vector3 translate = kf.Translate * weight * scale; node.Translate(translate); // interpolate between not rotation and full rotation, to point weight, so 0 = no rotate, and 1 = full rotation Quaternion rotate = Quaternion.Slerp(weight, Quaternion.Identity, kf.Rotation); node.Rotate(rotate); // TODO: not yet sure how to modify scale for cumulative animations Vector3 scaleVector = kf.Scale; // Not sure how to modify scale for cumulative anims... leave it alone //scaleVector = ((Vector3::UNIT_SCALE - kf.getScale()) * weight) + Vector3::UNIT_SCALE; if (scale != 1.0f && scaleVector != Vector3.UnitScale) scaleVector = Vector3.UnitScale + (scaleVector - Vector3.UnitScale) * scale; node.Scale(scaleVector); } else { // apply using weighted transform method node.WeightedTransform(weight, ref kf.translate, ref kf.rotation, ref kf.scale, false); } }
protected void ReadRotate(XmlNode node, TransformKeyFrame keyFrame) { float angle = float.Parse(node.Attributes["angle"].Value); foreach (XmlNode childNode in node.ChildNodes) { switch (childNode.Name) { case "axis": Vector3 axis = ReadVector3(childNode); keyFrame.Rotation = Quaternion.FromAngleAxis(angle, axis); break; default: DebugMessage(childNode); break; } } }
public static void CleanupAnimation(Skeleton skel, Animation anim) { Animation newAnim = skel.CreateAnimation("_replacement", anim.Length); Animation tmpAnim = skel.CreateAnimation("_temporary", anim.Length); foreach (NodeAnimationTrack track in anim.NodeTracks.Values) { Bone bone = skel.GetBone((ushort)track.Handle); NodeAnimationTrack newTrack = newAnim.CreateNodeTrack(track.Handle, bone); NodeAnimationTrack tmpTrack = tmpAnim.CreateNodeTrack(track.Handle, bone); int maxFrame = track.KeyFrames.Count; int lastKeyFrame = -1; for (int keyFrameIndex = 0; keyFrameIndex < track.KeyFrames.Count; ++keyFrameIndex) { if (anim.InterpolationMode == InterpolationMode.Linear) { // Linear is based on one point before and one after. TransformKeyFrame cur = track.GetTransformKeyFrame(keyFrameIndex); if (keyFrameIndex == 0 || keyFrameIndex == (track.KeyFrames.Count - 1)) { // Add the key frame if it is the first or last keyframe. lastKeyFrame = keyFrameIndex; DuplicateKeyFrame(newTrack, cur); } else { // Make sure tmpTrack is clean.. we just use it for interpolation tmpTrack.RemoveAllKeyFrames(); TransformKeyFrame prior = track.GetTransformKeyFrame(lastKeyFrame); TransformKeyFrame next = track.GetTransformKeyFrame(keyFrameIndex + 1); DuplicateKeyFrame(tmpTrack, prior); DuplicateKeyFrame(tmpTrack, next); // Check to see if removing this last keyframe will throw off // any of the other keyframes that were considered redundant. bool needKeyFrame = false; for (int i = lastKeyFrame + 1; i <= keyFrameIndex; ++i) { TransformKeyFrame orig = track.GetTransformKeyFrame(i); TransformKeyFrame interp = new TransformKeyFrame(tmpTrack, orig.Time); tmpTrack.GetInterpolatedKeyFrame(orig.Time, interp); // Is this interpolated frame useful or redundant? if (!CompareKeyFrames(interp, cur)) { needKeyFrame = true; break; } } if (needKeyFrame) { lastKeyFrame = keyFrameIndex; DuplicateKeyFrame(newTrack, cur); } } } else if (anim.InterpolationMode == InterpolationMode.Spline) { // Spline is based on two points before and two after. TransformKeyFrame cur = track.GetTransformKeyFrame(keyFrameIndex); #if DISABLED_CODE if (keyFrameIndex == 0 || keyFrameIndex == 1 || keyFrameIndex == (track.KeyFrames.Count - 1) || keyFrameIndex == (track.KeyFrames.Count - 2)) { // Add the key frame if it is the first, second, last or second to last keyframe. DuplicateKeyFrame(newTrack, cur); } else { // Make sure tmpTrack is clean.. we just use it for interpolation tmpTrack.RemoveAllKeyFrames(); TransformKeyFrame prior1 = track.GetTransformKeyFrame(keyFrameIndex - 2); TransformKeyFrame prior2 = track.GetTransformKeyFrame(keyFrameIndex - 1); TransformKeyFrame next1 = track.GetTransformKeyFrame(keyFrameIndex + 1); TransformKeyFrame next2 = track.GetTransformKeyFrame(keyFrameIndex + 2); DuplicateKeyFrame(tmpTrack, prior1); DuplicateKeyFrame(tmpTrack, prior2); DuplicateKeyFrame(tmpTrack, next1); DuplicateKeyFrame(tmpTrack, next2); TransformKeyFrame interp = new TransformKeyFrame(tmpTrack, cur.Time); tmpTrack.GetInterpolatedKeyFrame(cur.Time, interp); // Is this interpolated frame useful or redundant? if (!CompareKeyFrames(interp, cur)) DuplicateKeyFrame(newTrack, cur); } #else DuplicateKeyFrame(newTrack, cur); #endif } else { System.Diagnostics.Debug.Assert(false, "Invalid InterpolationMode: " + anim.InterpolationMode); } } } skel.RemoveAnimation(tmpAnim.Name); skel.RemoveAnimation(newAnim.Name); skel.RemoveAnimation(anim.Name); // Recreate the animation with the proper name (awkward) anim = skel.CreateAnimation(anim.Name, anim.Length); foreach (NodeAnimationTrack track in newAnim.NodeTracks.Values) { Bone bone = skel.GetBone((ushort)track.Handle); NodeAnimationTrack newTrack = anim.CreateNodeTrack(track.Handle, bone); foreach (KeyFrame keyFrame in track.KeyFrames) DuplicateKeyFrame(newTrack, (TransformKeyFrame)keyFrame); } }
public static void DuplicateKeyFrame(NodeAnimationTrack track, TransformKeyFrame orig) { TransformKeyFrame newKeyFrame = (TransformKeyFrame)track.CreateKeyFrame(orig.Time); newKeyFrame.Scale = orig.Scale; newKeyFrame.Rotation = orig.Rotation; newKeyFrame.Translate = orig.Translate; }
public static bool CompareKeyFrames(TransformKeyFrame keyFrame1, TransformKeyFrame keyFrame2) { Vector3 delta = keyFrame1.Translate - keyFrame2.Translate; if (delta.LengthSquared > (TranslateEpsilon * TranslateEpsilon)) return false; Quaternion rot = keyFrame1.Rotation * keyFrame2.Rotation.Inverse(); Vector3 axis = Vector3.Zero; float angle = 0; rot.ToAngleAxis(ref angle, ref axis); if (Math.Abs(angle) > RotateEpsilon) return false; delta = keyFrame1.Scale - keyFrame2.Scale; if (delta.LengthSquared > (ScaleEpsilon * ScaleEpsilon)) return false; return true; }
/// <summary> /// Gets a KeyFrame object which contains the interpolated transforms at the time index specified. /// </summary> /// <remarks> /// The KeyFrame objects held by this class are transformation snapshots at /// discrete points in time. Normally however, you want to interpolate between these /// keyframes to produce smooth movement, and this method allows you to do this easily. /// In animation terminology this is called 'tweening'. /// </remarks> /// <param name="time">The time (in relation to the whole animation sequence).</param> /// <returns> /// A new keyframe object containing the interpolated transforms. Note that the /// position and scaling transforms are linearly interpolated (lerp), whilst the rotation is /// spherically linearly interpolated (slerp) for the most natural result. /// </returns> public override KeyFrame GetInterpolatedKeyFrame(float time, KeyFrame kf) { // note: this is an un-attached keyframe TransformKeyFrame result = (TransformKeyFrame)kf; // Keyframe pointers KeyFrame kBase1, kBase2; TransformKeyFrame k1, k2; ushort firstKeyIndex; float t = GetKeyFramesAtTime(time, out kBase1, out kBase2, out firstKeyIndex); k1 = (TransformKeyFrame)kBase1; k2 = (TransformKeyFrame)kBase2; if (t == 0.0f) { // just use k1 result.Rotation = k1.Rotation; result.Translate = k1.Translate; result.Scale = k1.Scale; } else { // interpolate by t InterpolationMode mode = parent.interpolationMode; RotationInterpolationMode rim = parent.rotationInterpolationMode; switch (mode) { case InterpolationMode.Linear: { // linear interoplation // Rotation // Interpolate to nearest rotation if mUseShortestPath set if (rim == RotationInterpolationMode.Linear) { Quaternion.NlerpRef(ref result.rotation, t, ref k1.rotation, ref k2.rotation, useShortestPath); } else // RotationInterpolationMode.Spherical { result.rotation = Quaternion.Slerp(t, k1.rotation, k2.rotation, useShortestPath); } result.translate.x = k1.translate.x + ((k2.translate.x - k1.translate.x) * t); result.translate.y = k1.translate.y + ((k2.translate.y - k1.translate.y) * t); result.translate.z = k1.translate.z + ((k2.translate.z - k1.translate.z) * t); result.scale.x = k1.scale.x + ((k2.scale.x - k1.scale.x) * t); result.scale.y = k1.scale.y + ((k2.scale.y - k1.scale.y) * t); result.scale.z = k1.scale.z + ((k2.scale.z - k1.scale.z) * t); result.Changed(); } break; case InterpolationMode.Spline: { // spline interpolation if (isSplineRebuildNeeded) { BuildInterpolationSplines(); } result.Rotation = rotationSpline.Interpolate(firstKeyIndex, t, useShortestPath); result.Translate = positionSpline.Interpolate(firstKeyIndex, t); result.Scale = scaleSpline.Interpolate(firstKeyIndex, t); } break; } } // return the resulting keyframe return(result); }