//********************************** 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;
}
}
}
/** @} */
}