2 using System
.Collections
.Generic
;
5 namespace UnityEditor
.Rendering
.PostProcessing
7 internal sealed class CurveEditor
28 public struct Settings
31 public RectOffset padding
;
32 public Color selectionColor
;
33 public float curvePickingDistance
;
34 public float keyTimeClampingDistance
;
36 public static Settings defaultSettings
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
56 public uint minPointCount
;
57 public float zeroKeyConstantValue
;
60 public float handleWidth
;
61 public bool showNonEditableHandles
;
62 public bool onlyShowHandlesOnSelection
;
63 public bool loopInBounds
;
65 public static CurveState defaultState
74 zeroKeyConstantValue
= 0f
,
78 showNonEditableHandles
= true,
79 onlyShowHandlesOnSelection
= 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
)
95 this.keyframeIndex
= keyframeIndex
;
96 this.keyframe
= keyframe
;
100 internal struct MenuAction
102 internal SerializedProperty curve
;
104 internal Vector3 position
;
106 internal MenuAction(SerializedProperty curve
)
110 this.position
= Vector3
.zero
;
113 internal MenuAction(SerializedProperty curve
, int index
)
117 this.position
= Vector3
.zero
;
120 internal MenuAction(SerializedProperty curve
, Vector3 position
)
124 this.position
= position
;
130 #region Fields & properties
132 public Settings settings { get; private set; }
134 readonly Dictionary
<SerializedProperty
, CurveState
> m_Curves
;
137 SerializedProperty m_SelectedCurve
;
138 int m_SelectedKeyframeIndex
= -1;
140 EditMode m_EditMode
= EditMode
.None
;
141 Tangent m_TangentEditMode
;
147 #region Constructors & destructors
150 : this(Settings
.defaultSettings
)
153 public CurveEditor(Settings settings
)
155 this.settings
= settings
;
156 m_Curves
= new Dictionary
<SerializedProperty
, CurveState
>();
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()
197 public CurveState
GetCurveState(SerializedProperty curve
)
200 if (!m_Curves
.TryGetValue(curve
, out state
))
201 throw new KeyNotFoundException("curve");
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;
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
)
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
);
261 void OnCurveGUI(Rect rect
, SerializedProperty curve
, CurveState state
)
263 // Discard invisible curves
267 var animCurve
= curve
.animationCurveValue
;
268 var keys
= animCurve
.keys
;
269 var length
= keys
.Length
;
272 // Slightly dim non-editable curves
273 var color
= state
.color
;
274 if (!state
.editable
|| !GUI
.enabled
)
277 Handles
.color
= color
;
278 var bounds
= settings
.bounds
;
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
);
294 var prevKey
= keys
[0];
295 for (int k
= 1; k
< length
; 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
);
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
;
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
);
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];
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
);
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;
367 m_SelectedKeyframeIndex
= -1;
369 float enabledFactor
= GUI
.enabled
? 1f
: 0.8f
;
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
);
389 if (state
.editable
|| state
.showNonEditableHandles
)
391 if (e
.type
== EventType
.Repaint
)
393 var selectedColor
= (isCurrentlySelectedCurve
&& isCurrentlySelectedKeyframe
)
394 ? settings
.selectionColor
398 EditorGUI
.DrawRect(offset
.Remove(hitRect
), selectedColor
* enabledFactor
);
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
);
424 if (m_EditMode
== EditMode
.Moving
&& e
.type
== EventType
.MouseDrag
&& isCurrentlySelectedCurve
&& isCurrentlySelectedKeyframe
)
426 EditMoveKeyframe(animCurve
, keys
, k
);
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
))
443 SelectKeyframe(curve
, k
);
444 m_EditMode
= EditMode
.Moving
;
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();
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
;
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
;
486 // Mouse up - clean up states
487 if (e
.rawType
== EventType
.MouseUp
&& m_EditMode
!= EditMode
.None
)
489 m_EditMode
= EditMode
.None
;
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
;
514 if (e
.type
== EventType
.MouseDown
)
516 GUI
.FocusControl(null);
517 m_SelectedCurve
= null;
518 m_SelectedKeyframeIndex
= -1;
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
)
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();
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
)
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();
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
);
616 void SaveCurve(SerializedProperty prop
, AnimationCurve curve
)
618 prop
.animationCurveValue
= curve
;
628 #region Keyframe manipulations
630 void SelectKeyframe(SerializedProperty curve
, int keyframeIndex
)
632 m_SelectedKeyframeIndex
= keyframeIndex
;
633 m_SelectedCurve
= curve
;
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
)
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();
666 void EditCreateKeyframe(AnimationCurve curve
, Vector3 position
, bool createOnCurve
, float zeroKeyConstantValue
)
668 float tangent
= EvaluateTangent(curve
, position
.x
);
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
);
691 void RemoveKeyframe(AnimationCurve curve
, int keyframeIndex
)
693 curve
.RemoveKey(keyframeIndex
);
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
);
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
)
731 if (targetTangent
== Tangent
.Out
&& pos
.x
< 0f
)
736 if (Mathf
.Approximately(pos
.x
, 0f
))
737 tangent
= pos
.y
< 0f
? float.PositiveInfinity
: float.NegativeInfinity
;
739 tangent
= pos
.y
/ pos
.x
;
741 float inTangent
= keys
[keyframeIndex
].inTangent
;
742 float outTangent
= keys
[keyframeIndex
].outTangent
;
744 if (targetTangent
== Tangent
.In
|| linkTangents
)
746 if (targetTangent
== Tangent
.Out
|| linkTangents
)
747 outTangent
= tangent
;
749 SetKeyframe(curve
, keyframeIndex
, new Keyframe(time
, value, inTangent
, outTangent
));
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
;
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
);
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
)));
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
);
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
)
841 if (prev
== curve
.keys
.Length
- 1)
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
);