experiments with fresnel and shadergraph
[WindSway-HDRP.git] / Library / PackageCache / com.unity.postprocessing@2.1.6 / PostProcessing / Editor / Utils / CurveEditor.cs
blob72fac1d539e9bea627ea16d3e7ba469b944cfca9
1 using System;
2 using System.Collections.Generic;
3 using UnityEngine;
5 namespace UnityEditor.Rendering.PostProcessing
7 internal sealed class CurveEditor
9 #region Enums
11 enum EditMode
13 None,
14 Moving,
15 TangentEdit
18 enum Tangent
20 In,
21 Out
24 #endregion
26 #region Structs
28 public struct Settings
30 public Rect bounds;
31 public RectOffset padding;
32 public Color selectionColor;
33 public float curvePickingDistance;
34 public float keyTimeClampingDistance;
36 public static Settings defaultSettings
38 get
40 return new Settings
42 bounds = new Rect(0f, 0f, 1f, 1f),
43 padding = new RectOffset(10, 10, 10, 10),
44 selectionColor = Color.yellow,
45 curvePickingDistance = 6f,
46 keyTimeClampingDistance = 1e-4f
52 public struct CurveState
54 public bool visible;
55 public bool editable;
56 public uint minPointCount;
57 public float zeroKeyConstantValue;
58 public Color color;
59 public float width;
60 public float handleWidth;
61 public bool showNonEditableHandles;
62 public bool onlyShowHandlesOnSelection;
63 public bool loopInBounds;
65 public static CurveState defaultState
67 get
69 return new CurveState
71 visible = true,
72 editable = true,
73 minPointCount = 2,
74 zeroKeyConstantValue = 0f,
75 color = Color.white,
76 width = 2f,
77 handleWidth = 2f,
78 showNonEditableHandles = true,
79 onlyShowHandlesOnSelection = false,
80 loopInBounds = false
86 public struct Selection
88 public SerializedProperty curve;
89 public int keyframeIndex;
90 public Keyframe? keyframe;
92 public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe)
94 this.curve = curve;
95 this.keyframeIndex = keyframeIndex;
96 this.keyframe = keyframe;
100 internal struct MenuAction
102 internal SerializedProperty curve;
103 internal int index;
104 internal Vector3 position;
106 internal MenuAction(SerializedProperty curve)
108 this.curve = curve;
109 this.index = -1;
110 this.position = Vector3.zero;
113 internal MenuAction(SerializedProperty curve, int index)
115 this.curve = curve;
116 this.index = index;
117 this.position = Vector3.zero;
120 internal MenuAction(SerializedProperty curve, Vector3 position)
122 this.curve = curve;
123 this.index = -1;
124 this.position = position;
128 #endregion
130 #region Fields & properties
132 public Settings settings { get; private set; }
134 readonly Dictionary<SerializedProperty, CurveState> m_Curves;
135 Rect m_CurveArea;
137 SerializedProperty m_SelectedCurve;
138 int m_SelectedKeyframeIndex = -1;
140 EditMode m_EditMode = EditMode.None;
141 Tangent m_TangentEditMode;
143 bool m_Dirty;
145 #endregion
147 #region Constructors & destructors
149 public CurveEditor()
150 : this(Settings.defaultSettings)
153 public CurveEditor(Settings settings)
155 this.settings = settings;
156 m_Curves = new Dictionary<SerializedProperty, CurveState>();
159 #endregion
161 #region Public API
163 public void Add(params SerializedProperty[] curves)
165 foreach (var curve in curves)
166 Add(curve, CurveState.defaultState);
169 public void Add(SerializedProperty curve)
171 Add(curve, CurveState.defaultState);
174 public void Add(SerializedProperty curve, CurveState state)
176 // Make sure the property is in fact an AnimationCurve
177 var animCurve = curve.animationCurveValue;
178 if (animCurve == null)
179 throw new ArgumentException("curve");
181 if (m_Curves.ContainsKey(curve))
182 Debug.LogWarning("Curve has already been added to the editor");
184 m_Curves.Add(curve, state);
187 public void Remove(SerializedProperty curve)
189 m_Curves.Remove(curve);
192 public void RemoveAll()
194 m_Curves.Clear();
197 public CurveState GetCurveState(SerializedProperty curve)
199 CurveState state;
200 if (!m_Curves.TryGetValue(curve, out state))
201 throw new KeyNotFoundException("curve");
203 return state;
206 public void SetCurveState(SerializedProperty curve, CurveState state)
208 if (!m_Curves.ContainsKey(curve))
209 throw new KeyNotFoundException("curve");
211 m_Curves[curve] = state;
214 public Selection GetSelection()
216 Keyframe? key = null;
217 if (m_SelectedKeyframeIndex > -1)
219 var curve = m_SelectedCurve.animationCurveValue;
221 if (m_SelectedKeyframeIndex >= curve.length)
222 m_SelectedKeyframeIndex = -1;
223 else
224 key = curve[m_SelectedKeyframeIndex];
227 return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key);
230 public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe)
232 var animCurve = curve.animationCurveValue;
233 SetKeyframe(animCurve, keyframeIndex, keyframe);
234 SaveCurve(curve, animCurve);
237 public bool OnGUI(Rect rect)
239 if (Event.current.type == EventType.Repaint)
240 m_Dirty = false;
242 GUI.BeginClip(rect);
244 var area = new Rect(Vector2.zero, rect.size);
245 m_CurveArea = settings.padding.Remove(area);
247 foreach (var curve in m_Curves)
248 OnCurveGUI(area, curve.Key, curve.Value);
250 OnGeneralUI(area);
252 GUI.EndClip();
254 return m_Dirty;
257 #endregion
259 #region UI & events
261 void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state)
263 // Discard invisible curves
264 if (!state.visible)
265 return;
267 var animCurve = curve.animationCurveValue;
268 var keys = animCurve.keys;
269 var length = keys.Length;
271 // Curve drawing
272 // Slightly dim non-editable curves
273 var color = state.color;
274 if (!state.editable || !GUI.enabled)
275 color.a *= 0.5f;
277 Handles.color = color;
278 var bounds = settings.bounds;
280 if (length == 0)
282 var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue));
283 var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue));
284 Handles.DrawAAPolyLine(state.width, p1, p2);
286 else if (length == 1)
288 var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
289 var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value));
290 Handles.DrawAAPolyLine(state.width, p1, p2);
292 else
294 var prevKey = keys[0];
295 for (int k = 1; k < length; k++)
297 var key = keys[k];
298 var pts = BezierSegment(prevKey, key);
300 if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent))
302 var s = HardSegment(prevKey, key);
303 Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
305 else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
307 prevKey = key;
310 // Curve extents & loops
311 if (keys[0].time > bounds.xMin)
313 if (state.loopInBounds)
315 var p1 = keys[length - 1];
316 p1.time -= settings.bounds.width;
317 var p2 = keys[0];
318 var pts = BezierSegment(p1, p2);
320 if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
322 var s = HardSegment(p1, p2);
323 Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
325 else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
327 else
329 var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
330 var p2 = CurveToCanvas(keys[0]);
331 Handles.DrawAAPolyLine(state.width, p1, p2);
335 if (keys[length - 1].time < bounds.xMax)
337 if (state.loopInBounds)
339 var p1 = keys[length - 1];
340 var p2 = keys[0];
341 p2.time += settings.bounds.width;
342 var pts = BezierSegment(p1, p2);
344 if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
346 var s = HardSegment(p1, p2);
347 Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
349 else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
351 else
353 var p1 = CurveToCanvas(keys[length - 1]);
354 var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value));
355 Handles.DrawAAPolyLine(state.width, p1, p2);
360 // Make sure selection is correct (undo can break it)
361 bool isCurrentlySelectedCurve = curve == m_SelectedCurve;
363 if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length)
364 m_SelectedKeyframeIndex = -1;
366 if (!state.editable)
367 m_SelectedKeyframeIndex = -1;
369 float enabledFactor = GUI.enabled ? 1f : 0.8f;
371 // Handles & keys
372 for (int k = 0; k < length; k++)
374 bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex;
375 var e = Event.current;
377 var pos = CurveToCanvas(keys[k]);
378 var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f);
379 var offset = isCurrentlySelectedCurve
380 ? new RectOffset(5, 5, 5, 5)
381 : new RectOffset(6, 6, 6, 6);
383 var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f;
384 var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f;
385 var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f);
386 var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f);
388 // Draw
389 if (state.editable || state.showNonEditableHandles)
391 if (e.type == EventType.Repaint)
393 var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
394 ? settings.selectionColor
395 : state.color;
397 // Keyframe
398 EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor * enabledFactor);
400 // Tangents
401 if (isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe)))
403 Handles.color = selectedColor * enabledFactor;
405 if (k > 0 || state.loopInBounds)
407 Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent);
408 EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor);
411 if (k < length - 1 || state.loopInBounds)
413 Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent);
414 EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor);
420 // Events
421 if (state.editable)
423 // Keyframe move
424 if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
426 EditMoveKeyframe(animCurve, keys, k);
429 // Tangent editing
430 if (m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
432 bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent)));
433 EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control));
436 // Keyframe selection & context menu
437 if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
439 if (hitRect.Contains(e.mousePosition))
441 if (e.button == 0)
443 SelectKeyframe(curve, k);
444 m_EditMode = EditMode.Moving;
445 e.Use();
447 else if (e.button == 1)
449 // Keyframe context menu
450 var menu = new GenericMenu();
451 menu.AddItem(new GUIContent("Delete Key"), false, (x) =>
453 var action = (MenuAction)x;
454 var curveValue = action.curve.animationCurveValue;
455 action.curve.serializedObject.Update();
456 RemoveKeyframe(curveValue, action.index);
457 m_SelectedKeyframeIndex = -1;
458 SaveCurve(action.curve, curveValue);
459 action.curve.serializedObject.ApplyModifiedProperties();
460 }, new MenuAction(curve, k));
461 menu.ShowAsContext();
462 e.Use();
467 // Tangent selection & edit mode
468 if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
470 if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds))
472 SelectKeyframe(curve, k);
473 m_EditMode = EditMode.TangentEdit;
474 m_TangentEditMode = Tangent.In;
475 e.Use();
477 else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds))
479 SelectKeyframe(curve, k);
480 m_EditMode = EditMode.TangentEdit;
481 m_TangentEditMode = Tangent.Out;
482 e.Use();
486 // Mouse up - clean up states
487 if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None)
489 m_EditMode = EditMode.None;
492 // Set cursors
494 EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow);
496 if (k > 0 || state.loopInBounds)
497 EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow);
499 if (k < length - 1 || state.loopInBounds)
500 EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow);
505 Handles.color = Color.white;
506 SaveCurve(curve, animCurve);
509 void OnGeneralUI(Rect rect)
511 var e = Event.current;
513 // Selection
514 if (e.type == EventType.MouseDown)
516 GUI.FocusControl(null);
517 m_SelectedCurve = null;
518 m_SelectedKeyframeIndex = -1;
519 bool used = false;
521 var hit = CanvasToCurve(e.mousePosition);
522 float curvePickValue = CurveToCanvas(hit).y;
524 // Try and select a curve
525 foreach (var curve in m_Curves)
527 if (!curve.Value.editable || !curve.Value.visible)
528 continue;
530 var prop = curve.Key;
531 var state = curve.Value;
532 var animCurve = prop.animationCurveValue;
533 float hitY = animCurve.length == 0
534 ? state.zeroKeyConstantValue
535 : animCurve.Evaluate(hit.x);
537 var curvePos = CurveToCanvas(new Vector3(hit.x, hitY));
539 if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance)
541 m_SelectedCurve = prop;
543 if (e.clickCount == 2 && e.button == 0)
545 // Create a keyframe on double-click on this curve
546 EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue);
547 SaveCurve(prop, animCurve);
549 else if (e.button == 1)
551 // Curve context menu
552 var menu = new GenericMenu();
553 menu.AddItem(new GUIContent("Add Key"), false, (x) =>
555 var action = (MenuAction)x;
556 var curveValue = action.curve.animationCurveValue;
557 action.curve.serializedObject.Update();
558 EditCreateKeyframe(curveValue, hit, true, 0f);
559 SaveCurve(action.curve, curveValue);
560 action.curve.serializedObject.ApplyModifiedProperties();
561 }, new MenuAction(prop, hit));
562 menu.ShowAsContext();
563 e.Use();
564 used = true;
569 if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null)
571 // Create a keyframe on every curve on double-click
572 foreach (var curve in m_Curves)
574 if (!curve.Value.editable || !curve.Value.visible)
575 continue;
577 var prop = curve.Key;
578 var state = curve.Value;
579 var animCurve = prop.animationCurveValue;
580 EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue);
581 SaveCurve(prop, animCurve);
584 else if (!used && e.button == 1)
586 // Global context menu
587 var menu = new GenericMenu();
588 menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false));
589 menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true));
590 menu.ShowAsContext();
593 e.Use();
596 // Delete selected key(s)
597 if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace))
599 if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null)
601 var animCurve = m_SelectedCurve.animationCurveValue;
602 var length = animCurve.length;
604 if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0)
606 EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex);
607 m_SelectedKeyframeIndex = -1;
608 SaveCurve(m_SelectedCurve, animCurve);
611 e.Use();
616 void SaveCurve(SerializedProperty prop, AnimationCurve curve)
618 prop.animationCurveValue = curve;
621 void Invalidate()
623 m_Dirty = true;
626 #endregion
628 #region Keyframe manipulations
630 void SelectKeyframe(SerializedProperty curve, int keyframeIndex)
632 m_SelectedKeyframeIndex = keyframeIndex;
633 m_SelectedCurve = curve;
634 Invalidate();
637 void ContextMenuAddKey(Vector3 hit, bool createOnCurve)
639 SerializedObject serializedObject = null;
641 foreach (var curve in m_Curves)
643 if (!curve.Value.editable || !curve.Value.visible)
644 continue;
646 var prop = curve.Key;
647 var state = curve.Value;
649 if (serializedObject == null)
651 serializedObject = prop.serializedObject;
652 serializedObject.Update();
655 var animCurve = prop.animationCurveValue;
656 EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue);
657 SaveCurve(prop, animCurve);
660 if (serializedObject != null)
661 serializedObject.ApplyModifiedProperties();
663 Invalidate();
666 void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue)
668 float tangent = EvaluateTangent(curve, position.x);
670 if (createOnCurve)
672 position.y = curve.length == 0
673 ? zeroKeyConstantValue
674 : curve.Evaluate(position.x);
677 AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent));
680 void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex)
682 RemoveKeyframe(curve, keyframeIndex);
685 void AddKeyframe(AnimationCurve curve, Keyframe newValue)
687 curve.AddKey(newValue);
688 Invalidate();
691 void RemoveKeyframe(AnimationCurve curve, int keyframeIndex)
693 curve.RemoveKey(keyframeIndex);
694 Invalidate();
697 void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue)
699 var keys = curve.keys;
701 if (keyframeIndex > 0)
702 newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time);
704 if (keyframeIndex < keys.Length - 1)
705 newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time);
707 curve.MoveKey(keyframeIndex, newValue);
708 Invalidate();
711 void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex)
713 var key = CanvasToCurve(Event.current.mousePosition);
714 float inTgt = keys[keyframeIndex].inTangent;
715 float outTgt = keys[keyframeIndex].outTangent;
716 SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt));
719 void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents)
721 var pos = CanvasToCurve(Event.current.mousePosition);
723 float time = keys[keyframeIndex].time;
724 float value = keys[keyframeIndex].value;
726 pos -= new Vector3(time, value);
728 if (targetTangent == Tangent.In && pos.x > 0f)
729 pos.x = 0f;
731 if (targetTangent == Tangent.Out && pos.x < 0f)
732 pos.x = 0f;
734 float tangent;
736 if (Mathf.Approximately(pos.x, 0f))
737 tangent = pos.y < 0f ? float.PositiveInfinity : float.NegativeInfinity;
738 else
739 tangent = pos.y / pos.x;
741 float inTangent = keys[keyframeIndex].inTangent;
742 float outTangent = keys[keyframeIndex].outTangent;
744 if (targetTangent == Tangent.In || linkTangents)
745 inTangent = tangent;
746 if (targetTangent == Tangent.Out || linkTangents)
747 outTangent = tangent;
749 SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent));
752 #endregion
754 #region Maths utilities
756 Vector3 CurveToCanvas(Keyframe keyframe)
758 return CurveToCanvas(new Vector3(keyframe.time, keyframe.value));
761 Vector3 CurveToCanvas(Vector3 position)
763 var bounds = settings.bounds;
764 var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y));
765 output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin;
766 output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin;
767 return output;
770 Vector3 CanvasToCurve(Vector3 position)
772 var bounds = settings.bounds;
773 var output = position;
774 output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin);
775 output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin);
776 output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x);
777 output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y);
778 return output;
781 Vector3 CurveTangentToCanvas(float tangent)
783 if (!float.IsInfinity(tangent))
785 var bounds = settings.bounds;
786 float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y));
787 return new Vector3(1f, -tangent / ratio).normalized;
790 return float.IsPositiveInfinity(tangent) ? Vector3.up : Vector3.down;
793 Vector3[] BezierSegment(Keyframe start, Keyframe end)
795 var segment = new Vector3[4];
797 segment[0] = CurveToCanvas(new Vector3(start.time, start.value));
798 segment[3] = CurveToCanvas(new Vector3(end.time, end.value));
800 float middle = start.time + ((end.time - start.time) * 0.333333f);
801 float middle2 = start.time + ((end.time - start.time) * 0.666666f);
803 segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle)));
804 segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2)));
806 return segment;
809 Vector3[] HardSegment(Keyframe start, Keyframe end)
811 var segment = new Vector3[3];
813 segment[0] = CurveToCanvas(start);
814 segment[1] = CurveToCanvas(new Vector3(end.time, start.value));
815 segment[2] = CurveToCanvas(end);
817 return segment;
820 float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition)
822 return inValue + ((projPosition - inPosition) * inTangent);
825 float EvaluateTangent(AnimationCurve curve, float time)
827 int prev = -1, next = 0;
828 for (int i = 0; i < curve.keys.Length; i++)
830 if (time > curve.keys[i].time)
832 prev = i;
833 next = i + 1;
835 else break;
838 if (next == 0)
839 return 0f;
841 if (prev == curve.keys.Length - 1)
842 return 0f;
844 const float kD = 1e-3f;
845 float tp = Mathf.Max(time - kD, curve.keys[prev].time);
846 float tn = Mathf.Min(time + kD, curve.keys[next].time);
848 float vp = curve.Evaluate(tp);
849 float vn = curve.Evaluate(tn);
851 if (Mathf.Approximately(tn, tp))
852 return (vn - vp > 0f) ? float.PositiveInfinity : float.NegativeInfinity;
854 return (vn - vp) / (tn - tp);
857 #endregion