123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847 |
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- namespace UnityEditor.PostProcessing
- {
- public sealed class CurveEditor
- {
- #region Enums
- enum EditMode
- {
- None,
- Moving,
- TangentEdit
- }
- enum Tangent
- {
- In,
- Out
- }
- #endregion
- #region Structs
- public struct Settings
- {
- public Rect bounds;
- public RectOffset padding;
- public Color selectionColor;
- public float curvePickingDistance;
- public float keyTimeClampingDistance;
- public static Settings defaultSettings
- {
- get
- {
- return new Settings
- {
- bounds = new Rect(0f, 0f, 1f, 1f),
- padding = new RectOffset(10, 10, 10, 10),
- selectionColor = Color.yellow,
- curvePickingDistance = 6f,
- keyTimeClampingDistance = 1e-4f
- };
- }
- }
- }
- public struct CurveState
- {
- public bool visible;
- public bool editable;
- public uint minPointCount;
- public float zeroKeyConstantValue;
- public Color color;
- public float width;
- public float handleWidth;
- public bool showNonEditableHandles;
- public bool onlyShowHandlesOnSelection;
- public bool loopInBounds;
- public static CurveState defaultState
- {
- get
- {
- return new CurveState
- {
- visible = true,
- editable = true,
- minPointCount = 2,
- zeroKeyConstantValue = 0f,
- color = Color.white,
- width = 2f,
- handleWidth = 2f,
- showNonEditableHandles = true,
- onlyShowHandlesOnSelection = false,
- loopInBounds = false
- };
- }
- }
- }
- public struct Selection
- {
- public SerializedProperty curve;
- public int keyframeIndex;
- public Keyframe? keyframe;
- public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe)
- {
- this.curve = curve;
- this.keyframeIndex = keyframeIndex;
- this.keyframe = keyframe;
- }
- }
- internal struct MenuAction
- {
- internal SerializedProperty curve;
- internal int index;
- internal Vector3 position;
- internal MenuAction(SerializedProperty curve)
- {
- this.curve = curve;
- this.index = -1;
- this.position = Vector3.zero;
- }
- internal MenuAction(SerializedProperty curve, int index)
- {
- this.curve = curve;
- this.index = index;
- this.position = Vector3.zero;
- }
- internal MenuAction(SerializedProperty curve, Vector3 position)
- {
- this.curve = curve;
- this.index = -1;
- this.position = position;
- }
- }
- #endregion
- #region Fields & properties
- public Settings settings { get; private set; }
- Dictionary<SerializedProperty, CurveState> m_Curves;
- Rect m_CurveArea;
- SerializedProperty m_SelectedCurve;
- int m_SelectedKeyframeIndex = -1;
- EditMode m_EditMode = EditMode.None;
- Tangent m_TangentEditMode;
- bool m_Dirty;
- #endregion
- #region Constructors & destructors
- public CurveEditor()
- : this(Settings.defaultSettings)
- {}
- public CurveEditor(Settings settings)
- {
- this.settings = settings;
- m_Curves = new Dictionary<SerializedProperty, CurveState>();
- }
- #endregion
- #region Public API
- public void Add(params SerializedProperty[] curves)
- {
- foreach (var curve in curves)
- Add(curve, CurveState.defaultState);
- }
- public void Add(SerializedProperty curve)
- {
- Add(curve, CurveState.defaultState);
- }
- public void Add(SerializedProperty curve, CurveState state)
- {
- // Make sure the property is in fact an AnimationCurve
- var animCurve = curve.animationCurveValue;
- if (animCurve == null)
- throw new ArgumentException("curve");
- if (m_Curves.ContainsKey(curve))
- Debug.LogWarning("Curve has already been added to the editor");
- m_Curves.Add(curve, state);
- }
- public void Remove(SerializedProperty curve)
- {
- m_Curves.Remove(curve);
- }
- public void RemoveAll()
- {
- m_Curves.Clear();
- }
- public CurveState GetCurveState(SerializedProperty curve)
- {
- CurveState state;
- if (!m_Curves.TryGetValue(curve, out state))
- throw new KeyNotFoundException("curve");
- return state;
- }
- public void SetCurveState(SerializedProperty curve, CurveState state)
- {
- if (!m_Curves.ContainsKey(curve))
- throw new KeyNotFoundException("curve");
- m_Curves[curve] = state;
- }
- public Selection GetSelection()
- {
- Keyframe? key = null;
- if (m_SelectedKeyframeIndex > -1)
- {
- var curve = m_SelectedCurve.animationCurveValue;
- if (m_SelectedKeyframeIndex >= curve.length)
- m_SelectedKeyframeIndex = -1;
- else
- key = curve[m_SelectedKeyframeIndex];
- }
- return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key);
- }
- public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe)
- {
- var animCurve = curve.animationCurveValue;
- SetKeyframe(animCurve, keyframeIndex, keyframe);
- SaveCurve(curve, animCurve);
- }
- public bool OnGUI(Rect rect)
- {
- if (Event.current.type == EventType.Repaint)
- m_Dirty = false;
- GUI.BeginClip(rect);
- {
- var area = new Rect(Vector2.zero, rect.size);
- m_CurveArea = settings.padding.Remove(area);
- foreach (var curve in m_Curves)
- OnCurveGUI(area, curve.Key, curve.Value);
- OnGeneralUI(area);
- }
- GUI.EndClip();
- return m_Dirty;
- }
- #endregion
- #region UI & events
- void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state)
- {
- // Discard invisible curves
- if (!state.visible)
- return;
- var animCurve = curve.animationCurveValue;
- var keys = animCurve.keys;
- var length = keys.Length;
- // Curve drawing
- // Slightly dim non-editable curves
- var color = state.color;
- if (!state.editable)
- color.a *= 0.5f;
- Handles.color = color;
- var bounds = settings.bounds;
- if (length == 0)
- {
- var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue));
- var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue));
- Handles.DrawAAPolyLine(state.width, p1, p2);
- }
- else if (length == 1)
- {
- var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
- var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value));
- Handles.DrawAAPolyLine(state.width, p1, p2);
- }
- else
- {
- var prevKey = keys[0];
- for (int k = 1; k < length; k++)
- {
- var key = keys[k];
- var pts = BezierSegment(prevKey, key);
- if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent))
- {
- var s = HardSegment(prevKey, key);
- Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
- }
- else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
- prevKey = key;
- }
- // Curve extents & loops
- if (keys[0].time > bounds.xMin)
- {
- if (state.loopInBounds)
- {
- var p1 = keys[length - 1];
- p1.time -= settings.bounds.width;
- var p2 = keys[0];
- var pts = BezierSegment(p1, p2);
- if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
- {
- var s = HardSegment(p1, p2);
- Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
- }
- else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
- }
- else
- {
- var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
- var p2 = CurveToCanvas(keys[0]);
- Handles.DrawAAPolyLine(state.width, p1, p2);
- }
- }
- if (keys[length - 1].time < bounds.xMax)
- {
- if (state.loopInBounds)
- {
- var p1 = keys[length - 1];
- var p2 = keys[0];
- p2.time += settings.bounds.width;
- var pts = BezierSegment(p1, p2);
- if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
- {
- var s = HardSegment(p1, p2);
- Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
- }
- else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
- }
- else
- {
- var p1 = CurveToCanvas(keys[length - 1]);
- var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value));
- Handles.DrawAAPolyLine(state.width, p1, p2);
- }
- }
- }
- // Make sure selection is correct (undo can break it)
- bool isCurrentlySelectedCurve = curve == m_SelectedCurve;
- if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length)
- m_SelectedKeyframeIndex = -1;
- // Handles & keys
- for (int k = 0; k < length; k++)
- {
- bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex;
- var e = Event.current;
- var pos = CurveToCanvas(keys[k]);
- var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f);
- var offset = isCurrentlySelectedCurve
- ? new RectOffset(5, 5, 5, 5)
- : new RectOffset(6, 6, 6, 6);
- var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f;
- var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f;
- var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f);
- var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f);
- // Draw
- if (state.showNonEditableHandles)
- {
- if (e.type == EventType.Repaint)
- {
- var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
- ? settings.selectionColor
- : state.color;
- // Keyframe
- EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor);
- // Tangents
- if (isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe)))
- {
- Handles.color = selectedColor;
- if (k > 0 || state.loopInBounds)
- {
- Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent);
- EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor);
- }
- if (k < length - 1 || state.loopInBounds)
- {
- Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent);
- EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor);
- }
- }
- }
- }
- // Events
- if (state.editable)
- {
- // Keyframe move
- if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
- {
- EditMoveKeyframe(animCurve, keys, k);
- }
- // Tangent editing
- if (m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
- {
- bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent)));
- EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control));
- }
- // Keyframe selection & context menu
- if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
- {
- if (hitRect.Contains(e.mousePosition))
- {
- if (e.button == 0)
- {
- SelectKeyframe(curve, k);
- m_EditMode = EditMode.Moving;
- e.Use();
- }
- else if (e.button == 1)
- {
- // Keyframe context menu
- var menu = new GenericMenu();
- menu.AddItem(new GUIContent("Delete Key"), false, (x) =>
- {
- var action = (MenuAction)x;
- var curveValue = action.curve.animationCurveValue;
- action.curve.serializedObject.Update();
- RemoveKeyframe(curveValue, action.index);
- m_SelectedKeyframeIndex = -1;
- SaveCurve(action.curve, curveValue);
- action.curve.serializedObject.ApplyModifiedProperties();
- }, new MenuAction(curve, k));
- menu.ShowAsContext();
- e.Use();
- }
- }
- }
- // Tangent selection & edit mode
- if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
- {
- if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds))
- {
- SelectKeyframe(curve, k);
- m_EditMode = EditMode.TangentEdit;
- m_TangentEditMode = Tangent.In;
- e.Use();
- }
- else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds))
- {
- SelectKeyframe(curve, k);
- m_EditMode = EditMode.TangentEdit;
- m_TangentEditMode = Tangent.Out;
- e.Use();
- }
- }
- // Mouse up - clean up states
- if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None)
- {
- m_EditMode = EditMode.None;
- }
- // Set cursors
- {
- EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow);
- if (k > 0 || state.loopInBounds)
- EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow);
- if (k < length - 1 || state.loopInBounds)
- EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow);
- }
- }
- }
- Handles.color = Color.white;
- SaveCurve(curve, animCurve);
- }
- void OnGeneralUI(Rect rect)
- {
- var e = Event.current;
- // Selection
- if (e.type == EventType.MouseDown)
- {
- GUI.FocusControl(null);
- m_SelectedCurve = null;
- m_SelectedKeyframeIndex = -1;
- bool used = false;
- var hit = CanvasToCurve(e.mousePosition);
- float curvePickValue = CurveToCanvas(hit).y;
- // Try and select a curve
- foreach (var curve in m_Curves)
- {
- if (!curve.Value.editable || !curve.Value.visible)
- continue;
- var prop = curve.Key;
- var state = curve.Value;
- var animCurve = prop.animationCurveValue;
- float hitY = animCurve.length == 0
- ? state.zeroKeyConstantValue
- : animCurve.Evaluate(hit.x);
- var curvePos = CurveToCanvas(new Vector3(hit.x, hitY));
- if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance)
- {
- m_SelectedCurve = prop;
- if (e.clickCount == 2 && e.button == 0)
- {
- // Create a keyframe on double-click on this curve
- EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue);
- SaveCurve(prop, animCurve);
- }
- else if (e.button == 1)
- {
- // Curve context menu
- var menu = new GenericMenu();
- menu.AddItem(new GUIContent("Add Key"), false, (x) =>
- {
- var action = (MenuAction)x;
- var curveValue = action.curve.animationCurveValue;
- action.curve.serializedObject.Update();
- EditCreateKeyframe(curveValue, hit, true, 0f);
- SaveCurve(action.curve, curveValue);
- action.curve.serializedObject.ApplyModifiedProperties();
- }, new MenuAction(prop, hit));
- menu.ShowAsContext();
- e.Use();
- used = true;
- }
- }
- }
- if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null)
- {
- // Create a keyframe on every curve on double-click
- foreach (var curve in m_Curves)
- {
- if (!curve.Value.editable || !curve.Value.visible)
- continue;
- var prop = curve.Key;
- var state = curve.Value;
- var animCurve = prop.animationCurveValue;
- EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue);
- SaveCurve(prop, animCurve);
- }
- }
- else if (!used && e.button == 1)
- {
- // Global context menu
- var menu = new GenericMenu();
- menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false));
- menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true));
- menu.ShowAsContext();
- }
- e.Use();
- }
- // Delete selected key(s)
- if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace))
- {
- if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null)
- {
- var animCurve = m_SelectedCurve.animationCurveValue;
- var length = animCurve.length;
- if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0)
- {
- EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex);
- m_SelectedKeyframeIndex = -1;
- SaveCurve(m_SelectedCurve, animCurve);
- }
- e.Use();
- }
- }
- }
- void SaveCurve(SerializedProperty prop, AnimationCurve curve)
- {
- prop.animationCurveValue = curve;
- }
- void Invalidate()
- {
- m_Dirty = true;
- }
- #endregion
- #region Keyframe manipulations
- void SelectKeyframe(SerializedProperty curve, int keyframeIndex)
- {
- m_SelectedKeyframeIndex = keyframeIndex;
- m_SelectedCurve = curve;
- Invalidate();
- }
- void ContextMenuAddKey(Vector3 hit, bool createOnCurve)
- {
- SerializedObject serializedObject = null;
- foreach (var curve in m_Curves)
- {
- if (!curve.Value.editable || !curve.Value.visible)
- continue;
- var prop = curve.Key;
- var state = curve.Value;
- if (serializedObject == null)
- {
- serializedObject = prop.serializedObject;
- serializedObject.Update();
- }
- var animCurve = prop.animationCurveValue;
- EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue);
- SaveCurve(prop, animCurve);
- }
- if (serializedObject != null)
- serializedObject.ApplyModifiedProperties();
- Invalidate();
- }
- void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue)
- {
- float tangent = EvaluateTangent(curve, position.x);
- if (createOnCurve)
- {
- position.y = curve.length == 0
- ? zeroKeyConstantValue
- : curve.Evaluate(position.x);
- }
- AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent));
- }
- void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex)
- {
- RemoveKeyframe(curve, keyframeIndex);
- }
- void AddKeyframe(AnimationCurve curve, Keyframe newValue)
- {
- curve.AddKey(newValue);
- Invalidate();
- }
- void RemoveKeyframe(AnimationCurve curve, int keyframeIndex)
- {
- curve.RemoveKey(keyframeIndex);
- Invalidate();
- }
- void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue)
- {
- var keys = curve.keys;
- if (keyframeIndex > 0)
- newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time);
- if (keyframeIndex < keys.Length - 1)
- newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time);
- curve.MoveKey(keyframeIndex, newValue);
- Invalidate();
- }
- void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex)
- {
- var key = CanvasToCurve(Event.current.mousePosition);
- float inTgt = keys[keyframeIndex].inTangent;
- float outTgt = keys[keyframeIndex].outTangent;
- SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt));
- }
- void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents)
- {
- var pos = CanvasToCurve(Event.current.mousePosition);
- float time = keys[keyframeIndex].time;
- float value = keys[keyframeIndex].value;
- pos -= new Vector3(time, value);
- if (targetTangent == Tangent.In && pos.x > 0f)
- pos.x = 0f;
- if (targetTangent == Tangent.Out && pos.x < 0f)
- pos.x = 0f;
- float tangent;
- if (Mathf.Approximately(pos.x, 0f))
- tangent = pos.y < 0f ? float.PositiveInfinity : float.NegativeInfinity;
- else
- tangent = pos.y / pos.x;
- float inTangent = keys[keyframeIndex].inTangent;
- float outTangent = keys[keyframeIndex].outTangent;
- if (targetTangent == Tangent.In || linkTangents)
- inTangent = tangent;
- if (targetTangent == Tangent.Out || linkTangents)
- outTangent = tangent;
- SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent));
- }
- #endregion
- #region Maths utilities
- Vector3 CurveToCanvas(Keyframe keyframe)
- {
- return CurveToCanvas(new Vector3(keyframe.time, keyframe.value));
- }
- Vector3 CurveToCanvas(Vector3 position)
- {
- var bounds = settings.bounds;
- var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y));
- output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin;
- output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin;
- return output;
- }
- Vector3 CanvasToCurve(Vector3 position)
- {
- var bounds = settings.bounds;
- var output = position;
- output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin);
- output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin);
- output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x);
- output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y);
- return output;
- }
- Vector3 CurveTangentToCanvas(float tangent)
- {
- if (!float.IsInfinity(tangent))
- {
- var bounds = settings.bounds;
- float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y));
- return new Vector3(1f, -tangent / ratio).normalized;
- }
- return float.IsPositiveInfinity(tangent) ? Vector3.up : Vector3.down;
- }
- Vector3[] BezierSegment(Keyframe start, Keyframe end)
- {
- var segment = new Vector3[4];
- segment[0] = CurveToCanvas(new Vector3(start.time, start.value));
- segment[3] = CurveToCanvas(new Vector3(end.time, end.value));
- float middle = start.time + ((end.time - start.time) * 0.333333f);
- float middle2 = start.time + ((end.time - start.time) * 0.666666f);
- segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle)));
- segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2)));
- return segment;
- }
- Vector3[] HardSegment(Keyframe start, Keyframe end)
- {
- var segment = new Vector3[3];
- segment[0] = CurveToCanvas(start);
- segment[1] = CurveToCanvas(new Vector3(end.time, start.value));
- segment[2] = CurveToCanvas(end);
- return segment;
- }
- float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition)
- {
- return inValue + ((projPosition - inPosition) * inTangent);
- }
- float EvaluateTangent(AnimationCurve curve, float time)
- {
- int prev = -1, next = 0;
- for (int i = 0; i < curve.keys.Length; i++)
- {
- if (time > curve.keys[i].time)
- {
- prev = i;
- next = i + 1;
- }
- else break;
- }
- if (next == 0)
- return 0f;
- if (prev == curve.keys.Length - 1)
- return 0f;
- const float kD = 1e-3f;
- float tp = Mathf.Max(time - kD, curve.keys[prev].time);
- float tn = Mathf.Min(time + kD, curve.keys[next].time);
- float vp = curve.Evaluate(tp);
- float vn = curve.Evaluate(tn);
- if (Mathf.Approximately(tn, tp))
- return (vn - vp > 0f) ? float.PositiveInfinity : float.NegativeInfinity;
- return (vn - vp) / (tn - tp);
- }
- #endregion
- }
- }
|