//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// using System; using BansheeEngine; namespace BansheeEditor { internal enum TangentType { In = 1 << 0, Out = 1 << 1 } [Flags] internal enum TangentMode { Auto = 0, InAuto = TangentType.In | 1 << 2, InFree = TangentType.In | 1 << 3, InLinear = TangentType.In | 1 << 4, InStep = TangentType.In | 1 << 5, OutAuto = TangentType.Out | 1 << 6, OutFree = TangentType.Out | 1 << 7, OutLinear = TangentType.Out | 1 << 8, OutStep = TangentType.Out | 1 << 9, Free = 1 << 10, } internal class EdAnimationCurve { private AnimationCurve native; private KeyFrame[] keyFrames; private TangentMode[] tangentModes; public TangentMode[] TangentModes { get { return tangentModes; } } public KeyFrame[] KeyFrames { get { return keyFrames; } } internal EdAnimationCurve() { keyFrames = new KeyFrame[0]; native = new AnimationCurve(keyFrames); tangentModes = new TangentMode[0]; } // Tangent modes should match number of curve keyframes 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); } internal void AddKeyframe(float time, float value) { AddKeyframe(time, value, TangentMode.Auto); } 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; } 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 value and returns new keyframe index 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; } internal void SetTangentMode(int index, TangentMode mode) { if (index < 0 || index >= tangentModes.Length) return; tangentModes[index] = mode; } 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); } 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); } internal void Apply() { Array.Sort(keyFrames, (x, y) => { return x.time.CompareTo(y.time); }); UpdateTangents(); native.KeyFrames = keyFrames; } 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]; keyThis.inTangent = 0.0f; 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; } } } internal struct KeyframeRef { public KeyframeRef(int curveIdx, int keyIdx) { this.curveIdx = curveIdx; this.keyIdx = keyIdx; } public int curveIdx; public int keyIdx; } internal struct TangentRef { public TangentRef(KeyframeRef keyframeRef, TangentType type) { this.keyframeRef = keyframeRef; this.type = type; } public KeyframeRef keyframeRef; public TangentType type; } }