//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using bs; namespace bs.Editor { /** @addtogroup AnimationEditor * @{ */ /// /// wrapper for use in editor only. Allows easier manipulation of animation keyframes, and /// also stores keyframe tangent modes which are not required for non-editor curves. /// public class EdAnimationCurve { private AnimationCurve native; private KeyFrame[] keyFrames; private TangentMode[] tangentModes; /// /// Returns tangent modes for each keyframe. Array is guaranteed to be the same size as . /// If modifying the array values, make sure to call to save the changes on the curve. /// public TangentMode[] TangentModes { get { return tangentModes; } } /// /// All keyframes belonging to the animation curve. If modifying the keyframe values, make sure to call /// to save the changes on the curve. /// public KeyFrame[] KeyFrames { get { return keyFrames; } } /// /// Returns the non-editor version of the curve. /// public AnimationCurve Normal { get { return native; } } /// /// Creates a new animation curve with zero keyframes. /// internal EdAnimationCurve() { keyFrames = new KeyFrame[0]; native = new AnimationCurve(keyFrames); tangentModes = new TangentMode[0]; } /// /// Creates a new editor animation curve using an existing animation curve as a basis. /// /// Animation curve to retrieve the keyframes from. /// A set of tangent modes for each keyframe. Should be the same size as the number /// of keyframes in the provided animation. Can be null in which case all keyframes will /// have tangents set to automatic. internal EdAnimationCurve(AnimationCurve native, TangentMode[] tangentModes) { this.native = native; keyFrames = native.KeyFrames; this.tangentModes = new TangentMode[keyFrames.Length]; if (tangentModes != null) { int numTangents = Math.Min(keyFrames.Length, tangentModes.Length); Array.Copy(tangentModes, this.tangentModes, numTangents); } Apply(); } /// /// Evaluate the animation curve at the specified time. /// /// Time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beggining. Otherwise the curve /// value will be clamped. /// Interpolated value from the curve at provided time. internal float Evaluate(float time, bool loop = true) { return native.Evaluate(time, loop); } /// /// Adds a new keyframe to the animation curve, unless a keyframe with the same time already exists in which case /// the existing keyframe is updated with the new value. Newly added keyframe will use the automatic tangent mode. /// /// Time at which to add/update the keyframe. /// Value of the keyframe. internal void AddOrUpdateKeyframe(float time, float value) { int keyframeIdx = -1; for (int i = 0; i < keyFrames.Length; i++) { if (MathEx.ApproxEquals(keyFrames[i].time, time)) { keyframeIdx = i; break; } } if (keyframeIdx != -1) UpdateKeyframe(keyframeIdx, time, value); else AddKeyframe(time, value); } /// /// Adds a new keyframe to the animation curve. Keyframe will use the automatic tangent mode. /// /// Time at which to add the keyframe. /// Value of the keyframe. internal void AddKeyframe(float time, float value) { AddKeyframe(time, value, TangentMode.Auto); } /// /// Adds a new keyframe to the animation curve. /// /// Time at which to add the keyframe. /// Value of the keyframe. /// Tangent mode of the keyframe. internal void AddKeyframe(float time, float value, TangentMode tangentMode) { KeyFrame[] newKeyFrames = new KeyFrame[keyFrames.Length + 1]; newKeyFrames[newKeyFrames.Length - 1].time = float.PositiveInfinity; TangentMode[] newTangentModes = new TangentMode[tangentModes.Length + 1]; int insertIdx = keyFrames.Length; for (int i = 0; i < keyFrames.Length; i++) { if (time < keyFrames[i].time) { insertIdx = i; break; } } Array.Copy(keyFrames, newKeyFrames, insertIdx); Array.Copy(tangentModes, newTangentModes, insertIdx); KeyFrame keyFrame = new KeyFrame(); keyFrame.time = time; keyFrame.value = value; newKeyFrames[insertIdx] = keyFrame; newTangentModes[insertIdx] = tangentMode; if (insertIdx < keyFrames.Length) { int remaining = keyFrames.Length - insertIdx; Array.Copy(keyFrames, insertIdx, newKeyFrames, insertIdx + 1, remaining); Array.Copy(tangentModes, insertIdx, newTangentModes, insertIdx + 1, remaining); } tangentModes = newTangentModes; keyFrames = newKeyFrames; } /// /// Removes a keyframe at the specified index. /// /// Index of the keyframe, referencing the array. internal void RemoveKeyframe(int index) { if (index < 0 || index >= KeyFrames.Length) return; KeyFrame[] newKeyFrames = new KeyFrame[KeyFrames.Length - 1]; TangentMode[] newTangentModes = new TangentMode[tangentModes.Length - 1]; Array.Copy(KeyFrames, newKeyFrames, index); Array.Copy(tangentModes, newTangentModes, index); if (index < newKeyFrames.Length) { int remaining = newKeyFrames.Length - index; Array.Copy(KeyFrames, index + 1, newKeyFrames, index, remaining); Array.Copy(tangentModes, index + 1, newTangentModes, index, remaining); } tangentModes = newTangentModes; keyFrames = newKeyFrames; } /// /// Updates key-frame time and value. Since keyframes are ordered by time the index of the keyframe might change, /// so a new index of the keyframe is returned by this method. /// /// Index of the keyframe to update, referencing the array. /// Time to which to set the keyframe. /// Value of the keyframe. /// New index of the keyframe, referencing the array. internal int UpdateKeyframe(int index, float time, float value) { if (index < 0 || index >= keyFrames.Length) return -1; keyFrames[index].time = time; keyFrames[index].value = value; // Check if key moved before or after other keys. Animation curve automatically sorts // keys and if this happens our key indices will change. So we sort it here and modify // indices. int currentKeyIndex = index; int prevKeyIdx = currentKeyIndex - 1; while (prevKeyIdx >= 0) { if (time >= keyFrames[prevKeyIdx].time) break; KeyFrame temp = keyFrames[prevKeyIdx]; keyFrames[prevKeyIdx] = keyFrames[currentKeyIndex]; keyFrames[currentKeyIndex] = temp; TangentMode tempMode = tangentModes[prevKeyIdx]; tangentModes[prevKeyIdx] = tangentModes[currentKeyIndex]; tangentModes[currentKeyIndex] = tempMode; currentKeyIndex = prevKeyIdx; prevKeyIdx--; } int nextKeyIdx = currentKeyIndex + 1; while (nextKeyIdx < keyFrames.Length) { if (time <= keyFrames[nextKeyIdx].time) break; KeyFrame temp = keyFrames[nextKeyIdx]; keyFrames[nextKeyIdx] = keyFrames[currentKeyIndex]; keyFrames[currentKeyIndex] = temp; TangentMode tempMode = tangentModes[nextKeyIdx]; tangentModes[nextKeyIdx] = tangentModes[currentKeyIndex]; tangentModes[currentKeyIndex] = tempMode; currentKeyIndex = nextKeyIdx; nextKeyIdx++; } return currentKeyIndex; } /// /// Changes the tangent mode of a keyframe at the specified index. /// /// Index of the keyframe to update, referencing the array. /// New tangent mode of the keyframe. internal void SetTangentMode(int index, TangentMode mode) { if (index < 0 || index >= tangentModes.Length) return; tangentModes[index] = mode; } /// /// Converts a keyframe tangent (slope) value into a 2D normal vector. /// /// Keyframe tangent (slope). /// Normalized 2D vector pointing in the direction of the tangent. internal static Vector2 TangentToNormal(float tangent) { if(tangent == float.PositiveInfinity) return new Vector2(0, 1); Vector2 normal = new Vector2(1, tangent); return Vector2.Normalize(normal); } /// /// Converts a 2D normal vector into a keyframe tangent (slope). /// /// Normalized 2D vector pointing in the direction of the tangent. /// Keyframe tangent (slope). internal static float NormalToTangent(Vector2 normal) { // We know the X value must be one, use that to deduce pre-normalized length float length = 1/normal.x; // Use length to deduce the tangent (y coordinate) return MathEx.Sqrt(length*length - 1) * MathEx.Sign(normal.y); } /// /// Applies the changes of the editor curve, to the actual underlying animation curve. /// internal void Apply() { Array.Sort(keyFrames, (x, y) => { return x.time.CompareTo(y.time); }); UpdateTangents(); native = new AnimationCurve(keyFrames); } /// /// Recalculates tangents for all keyframes using the keyframe values and set tangent modes. /// private void UpdateTangents() { if (keyFrames.Length == 0) return; if (keyFrames.Length == 1) { keyFrames[0].inTangent = 0.0f; keyFrames[0].outTangent = 0.0f; return; } // First keyframe { KeyFrame keyThis = keyFrames[0]; KeyFrame keyNext = keyFrames[1]; keyThis.inTangent = 0.0f; TangentMode tangentMode = tangentModes[0]; if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.OutAuto) || tangentMode.HasFlag(TangentMode.OutLinear)) { float diff = keyNext.time - keyThis.time; if(!MathEx.ApproxEquals(diff, 0.0f)) keyThis.outTangent = (keyNext.value - keyThis.value) / diff; else keyThis.outTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.OutStep)) { keyThis.outTangent = float.PositiveInfinity; } keyFrames[0] = keyThis; } // Inner keyframes for(int i = 1; i < keyFrames.Length - 1; i++) { KeyFrame keyPrev = keyFrames[i - 1]; KeyFrame keyThis = keyFrames[i]; KeyFrame keyNext = keyFrames[i + 1]; TangentMode tangentMode = tangentModes[i]; if (tangentMode == TangentMode.Auto) // Both automatic { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.outTangent = (keyNext.value - keyPrev.value) / diff; else keyThis.outTangent = float.PositiveInfinity; keyThis.inTangent = keyThis.outTangent; } else if (tangentMode == TangentMode.Free) // Both free { keyThis.inTangent = keyThis.outTangent; } else // Different per-tangent modes { // In tangent if (tangentMode.HasFlag(TangentMode.InAuto)) { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.inTangent = (keyNext.value - keyPrev.value)/diff; else keyThis.inTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.InLinear)) { float diff = keyThis.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.inTangent = (keyThis.value - keyPrev.value) / diff; else keyThis.inTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.InStep)) { keyThis.inTangent = float.PositiveInfinity; } // Out tangent if (tangentMode.HasFlag(TangentMode.OutAuto)) { float diff = keyNext.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.outTangent = (keyNext.value - keyPrev.value) / diff; else keyThis.outTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.OutLinear)) { float diff = keyNext.time - keyThis.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.outTangent = (keyNext.value - keyThis.value) / diff; else keyThis.outTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.OutStep)) { keyThis.outTangent = float.PositiveInfinity; } } keyFrames[i] = keyThis; } // Last keyframe { KeyFrame keyThis = keyFrames[keyFrames.Length - 1]; KeyFrame keyPrev = keyFrames[keyFrames.Length - 2]; keyThis.outTangent = 0.0f; TangentMode tangentMode = tangentModes[tangentModes.Length - 1]; if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.InAuto) || tangentMode.HasFlag(TangentMode.InLinear)) { float diff = keyThis.time - keyPrev.time; if (!MathEx.ApproxEquals(diff, 0.0f)) keyThis.inTangent = (keyThis.value - keyPrev.value)/diff; else keyThis.inTangent = float.PositiveInfinity; } else if (tangentMode.HasFlag(TangentMode.InStep)) { keyThis.inTangent = float.PositiveInfinity; } keyFrames[keyFrames.Length - 1] = keyThis; } } } /** @} */ }