You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

847 lines
30 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace UnityEditor.PostProcessing
  5. {
  6. public sealed class CurveEditor
  7. {
  8. #region Enums
  9. enum EditMode
  10. {
  11. None,
  12. Moving,
  13. TangentEdit
  14. }
  15. enum Tangent
  16. {
  17. In,
  18. Out
  19. }
  20. #endregion
  21. #region Structs
  22. public struct Settings
  23. {
  24. public Rect bounds;
  25. public RectOffset padding;
  26. public Color selectionColor;
  27. public float curvePickingDistance;
  28. public float keyTimeClampingDistance;
  29. public static Settings defaultSettings
  30. {
  31. get
  32. {
  33. return new Settings
  34. {
  35. bounds = new Rect(0f, 0f, 1f, 1f),
  36. padding = new RectOffset(10, 10, 10, 10),
  37. selectionColor = Color.yellow,
  38. curvePickingDistance = 6f,
  39. keyTimeClampingDistance = 1e-4f
  40. };
  41. }
  42. }
  43. }
  44. public struct CurveState
  45. {
  46. public bool visible;
  47. public bool editable;
  48. public uint minPointCount;
  49. public float zeroKeyConstantValue;
  50. public Color color;
  51. public float width;
  52. public float handleWidth;
  53. public bool showNonEditableHandles;
  54. public bool onlyShowHandlesOnSelection;
  55. public bool loopInBounds;
  56. public static CurveState defaultState
  57. {
  58. get
  59. {
  60. return new CurveState
  61. {
  62. visible = true,
  63. editable = true,
  64. minPointCount = 2,
  65. zeroKeyConstantValue = 0f,
  66. color = Color.white,
  67. width = 2f,
  68. handleWidth = 2f,
  69. showNonEditableHandles = true,
  70. onlyShowHandlesOnSelection = false,
  71. loopInBounds = false
  72. };
  73. }
  74. }
  75. }
  76. public struct Selection
  77. {
  78. public SerializedProperty curve;
  79. public int keyframeIndex;
  80. public Keyframe? keyframe;
  81. public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe)
  82. {
  83. this.curve = curve;
  84. this.keyframeIndex = keyframeIndex;
  85. this.keyframe = keyframe;
  86. }
  87. }
  88. internal struct MenuAction
  89. {
  90. internal SerializedProperty curve;
  91. internal int index;
  92. internal Vector3 position;
  93. internal MenuAction(SerializedProperty curve)
  94. {
  95. this.curve = curve;
  96. this.index = -1;
  97. this.position = Vector3.zero;
  98. }
  99. internal MenuAction(SerializedProperty curve, int index)
  100. {
  101. this.curve = curve;
  102. this.index = index;
  103. this.position = Vector3.zero;
  104. }
  105. internal MenuAction(SerializedProperty curve, Vector3 position)
  106. {
  107. this.curve = curve;
  108. this.index = -1;
  109. this.position = position;
  110. }
  111. }
  112. #endregion
  113. #region Fields & properties
  114. public Settings settings { get; private set; }
  115. Dictionary<SerializedProperty, CurveState> m_Curves;
  116. Rect m_CurveArea;
  117. SerializedProperty m_SelectedCurve;
  118. int m_SelectedKeyframeIndex = -1;
  119. EditMode m_EditMode = EditMode.None;
  120. Tangent m_TangentEditMode;
  121. bool m_Dirty;
  122. #endregion
  123. #region Constructors & destructors
  124. public CurveEditor()
  125. : this(Settings.defaultSettings)
  126. {}
  127. public CurveEditor(Settings settings)
  128. {
  129. this.settings = settings;
  130. m_Curves = new Dictionary<SerializedProperty, CurveState>();
  131. }
  132. #endregion
  133. #region Public API
  134. public void Add(params SerializedProperty[] curves)
  135. {
  136. foreach (var curve in curves)
  137. Add(curve, CurveState.defaultState);
  138. }
  139. public void Add(SerializedProperty curve)
  140. {
  141. Add(curve, CurveState.defaultState);
  142. }
  143. public void Add(SerializedProperty curve, CurveState state)
  144. {
  145. // Make sure the property is in fact an AnimationCurve
  146. var animCurve = curve.animationCurveValue;
  147. if (animCurve == null)
  148. throw new ArgumentException("curve");
  149. if (m_Curves.ContainsKey(curve))
  150. Debug.LogWarning("Curve has already been added to the editor");
  151. m_Curves.Add(curve, state);
  152. }
  153. public void Remove(SerializedProperty curve)
  154. {
  155. m_Curves.Remove(curve);
  156. }
  157. public void RemoveAll()
  158. {
  159. m_Curves.Clear();
  160. }
  161. public CurveState GetCurveState(SerializedProperty curve)
  162. {
  163. CurveState state;
  164. if (!m_Curves.TryGetValue(curve, out state))
  165. throw new KeyNotFoundException("curve");
  166. return state;
  167. }
  168. public void SetCurveState(SerializedProperty curve, CurveState state)
  169. {
  170. if (!m_Curves.ContainsKey(curve))
  171. throw new KeyNotFoundException("curve");
  172. m_Curves[curve] = state;
  173. }
  174. public Selection GetSelection()
  175. {
  176. Keyframe? key = null;
  177. if (m_SelectedKeyframeIndex > -1)
  178. {
  179. var curve = m_SelectedCurve.animationCurveValue;
  180. if (m_SelectedKeyframeIndex >= curve.length)
  181. m_SelectedKeyframeIndex = -1;
  182. else
  183. key = curve[m_SelectedKeyframeIndex];
  184. }
  185. return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key);
  186. }
  187. public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe)
  188. {
  189. var animCurve = curve.animationCurveValue;
  190. SetKeyframe(animCurve, keyframeIndex, keyframe);
  191. SaveCurve(curve, animCurve);
  192. }
  193. public bool OnGUI(Rect rect)
  194. {
  195. if (Event.current.type == EventType.Repaint)
  196. m_Dirty = false;
  197. GUI.BeginClip(rect);
  198. {
  199. var area = new Rect(Vector2.zero, rect.size);
  200. m_CurveArea = settings.padding.Remove(area);
  201. foreach (var curve in m_Curves)
  202. OnCurveGUI(area, curve.Key, curve.Value);
  203. OnGeneralUI(area);
  204. }
  205. GUI.EndClip();
  206. return m_Dirty;
  207. }
  208. #endregion
  209. #region UI & events
  210. void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state)
  211. {
  212. // Discard invisible curves
  213. if (!state.visible)
  214. return;
  215. var animCurve = curve.animationCurveValue;
  216. var keys = animCurve.keys;
  217. var length = keys.Length;
  218. // Curve drawing
  219. // Slightly dim non-editable curves
  220. var color = state.color;
  221. if (!state.editable)
  222. color.a *= 0.5f;
  223. Handles.color = color;
  224. var bounds = settings.bounds;
  225. if (length == 0)
  226. {
  227. var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue));
  228. var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue));
  229. Handles.DrawAAPolyLine(state.width, p1, p2);
  230. }
  231. else if (length == 1)
  232. {
  233. var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
  234. var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value));
  235. Handles.DrawAAPolyLine(state.width, p1, p2);
  236. }
  237. else
  238. {
  239. var prevKey = keys[0];
  240. for (int k = 1; k < length; k++)
  241. {
  242. var key = keys[k];
  243. var pts = BezierSegment(prevKey, key);
  244. if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent))
  245. {
  246. var s = HardSegment(prevKey, key);
  247. Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
  248. }
  249. else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
  250. prevKey = key;
  251. }
  252. // Curve extents & loops
  253. if (keys[0].time > bounds.xMin)
  254. {
  255. if (state.loopInBounds)
  256. {
  257. var p1 = keys[length - 1];
  258. p1.time -= settings.bounds.width;
  259. var p2 = keys[0];
  260. var pts = BezierSegment(p1, p2);
  261. if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
  262. {
  263. var s = HardSegment(p1, p2);
  264. Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
  265. }
  266. else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
  267. }
  268. else
  269. {
  270. var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
  271. var p2 = CurveToCanvas(keys[0]);
  272. Handles.DrawAAPolyLine(state.width, p1, p2);
  273. }
  274. }
  275. if (keys[length - 1].time < bounds.xMax)
  276. {
  277. if (state.loopInBounds)
  278. {
  279. var p1 = keys[length - 1];
  280. var p2 = keys[0];
  281. p2.time += settings.bounds.width;
  282. var pts = BezierSegment(p1, p2);
  283. if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
  284. {
  285. var s = HardSegment(p1, p2);
  286. Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
  287. }
  288. else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
  289. }
  290. else
  291. {
  292. var p1 = CurveToCanvas(keys[length - 1]);
  293. var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value));
  294. Handles.DrawAAPolyLine(state.width, p1, p2);
  295. }
  296. }
  297. }
  298. // Make sure selection is correct (undo can break it)
  299. bool isCurrentlySelectedCurve = curve == m_SelectedCurve;
  300. if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length)
  301. m_SelectedKeyframeIndex = -1;
  302. // Handles & keys
  303. for (int k = 0; k < length; k++)
  304. {
  305. bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex;
  306. var e = Event.current;
  307. var pos = CurveToCanvas(keys[k]);
  308. var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f);
  309. var offset = isCurrentlySelectedCurve
  310. ? new RectOffset(5, 5, 5, 5)
  311. : new RectOffset(6, 6, 6, 6);
  312. var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f;
  313. var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f;
  314. var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f);
  315. var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f);
  316. // Draw
  317. if (state.showNonEditableHandles)
  318. {
  319. if (e.type == EventType.Repaint)
  320. {
  321. var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
  322. ? settings.selectionColor
  323. : state.color;
  324. // Keyframe
  325. EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor);
  326. // Tangents
  327. if (isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe)))
  328. {
  329. Handles.color = selectedColor;
  330. if (k > 0 || state.loopInBounds)
  331. {
  332. Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent);
  333. EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor);
  334. }
  335. if (k < length - 1 || state.loopInBounds)
  336. {
  337. Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent);
  338. EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor);
  339. }
  340. }
  341. }
  342. }
  343. // Events
  344. if (state.editable)
  345. {
  346. // Keyframe move
  347. if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
  348. {
  349. EditMoveKeyframe(animCurve, keys, k);
  350. }
  351. // Tangent editing
  352. if (m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
  353. {
  354. bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent)));
  355. EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control));
  356. }
  357. // Keyframe selection & context menu
  358. if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
  359. {
  360. if (hitRect.Contains(e.mousePosition))
  361. {
  362. if (e.button == 0)
  363. {
  364. SelectKeyframe(curve, k);
  365. m_EditMode = EditMode.Moving;
  366. e.Use();
  367. }
  368. else if (e.button == 1)
  369. {
  370. // Keyframe context menu
  371. var menu = new GenericMenu();
  372. menu.AddItem(new GUIContent("Delete Key"), false, (x) =>
  373. {
  374. var action = (MenuAction)x;
  375. var curveValue = action.curve.animationCurveValue;
  376. action.curve.serializedObject.Update();
  377. RemoveKeyframe(curveValue, action.index);
  378. m_SelectedKeyframeIndex = -1;
  379. SaveCurve(action.curve, curveValue);
  380. action.curve.serializedObject.ApplyModifiedProperties();
  381. }, new MenuAction(curve, k));
  382. menu.ShowAsContext();
  383. e.Use();
  384. }
  385. }
  386. }
  387. // Tangent selection & edit mode
  388. if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
  389. {
  390. if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds))
  391. {
  392. SelectKeyframe(curve, k);
  393. m_EditMode = EditMode.TangentEdit;
  394. m_TangentEditMode = Tangent.In;
  395. e.Use();
  396. }
  397. else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds))
  398. {
  399. SelectKeyframe(curve, k);
  400. m_EditMode = EditMode.TangentEdit;
  401. m_TangentEditMode = Tangent.Out;
  402. e.Use();
  403. }
  404. }
  405. // Mouse up - clean up states
  406. if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None)
  407. {
  408. m_EditMode = EditMode.None;
  409. }
  410. // Set cursors
  411. {
  412. EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow);
  413. if (k > 0 || state.loopInBounds)
  414. EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow);
  415. if (k < length - 1 || state.loopInBounds)
  416. EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow);
  417. }
  418. }
  419. }
  420. Handles.color = Color.white;
  421. SaveCurve(curve, animCurve);
  422. }
  423. void OnGeneralUI(Rect rect)
  424. {
  425. var e = Event.current;
  426. // Selection
  427. if (e.type == EventType.MouseDown)
  428. {
  429. GUI.FocusControl(null);
  430. m_SelectedCurve = null;
  431. m_SelectedKeyframeIndex = -1;
  432. bool used = false;
  433. var hit = CanvasToCurve(e.mousePosition);
  434. float curvePickValue = CurveToCanvas(hit).y;
  435. // Try and select a curve
  436. foreach (var curve in m_Curves)
  437. {
  438. if (!curve.Value.editable || !curve.Value.visible)
  439. continue;
  440. var prop = curve.Key;
  441. var state = curve.Value;
  442. var animCurve = prop.animationCurveValue;
  443. float hitY = animCurve.length == 0
  444. ? state.zeroKeyConstantValue
  445. : animCurve.Evaluate(hit.x);
  446. var curvePos = CurveToCanvas(new Vector3(hit.x, hitY));
  447. if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance)
  448. {
  449. m_SelectedCurve = prop;
  450. if (e.clickCount == 2 && e.button == 0)
  451. {
  452. // Create a keyframe on double-click on this curve
  453. EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue);
  454. SaveCurve(prop, animCurve);
  455. }
  456. else if (e.button == 1)
  457. {
  458. // Curve context menu
  459. var menu = new GenericMenu();
  460. menu.AddItem(new GUIContent("Add Key"), false, (x) =>
  461. {
  462. var action = (MenuAction)x;
  463. var curveValue = action.curve.animationCurveValue;
  464. action.curve.serializedObject.Update();
  465. EditCreateKeyframe(curveValue, hit, true, 0f);
  466. SaveCurve(action.curve, curveValue);
  467. action.curve.serializedObject.ApplyModifiedProperties();
  468. }, new MenuAction(prop, hit));
  469. menu.ShowAsContext();
  470. e.Use();
  471. used = true;
  472. }
  473. }
  474. }
  475. if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null)
  476. {
  477. // Create a keyframe on every curve on double-click
  478. foreach (var curve in m_Curves)
  479. {
  480. if (!curve.Value.editable || !curve.Value.visible)
  481. continue;
  482. var prop = curve.Key;
  483. var state = curve.Value;
  484. var animCurve = prop.animationCurveValue;
  485. EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue);
  486. SaveCurve(prop, animCurve);
  487. }
  488. }
  489. else if (!used && e.button == 1)
  490. {
  491. // Global context menu
  492. var menu = new GenericMenu();
  493. menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false));
  494. menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true));
  495. menu.ShowAsContext();
  496. }
  497. e.Use();
  498. }
  499. // Delete selected key(s)
  500. if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace))
  501. {
  502. if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null)
  503. {
  504. var animCurve = m_SelectedCurve.animationCurveValue;
  505. var length = animCurve.length;
  506. if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0)
  507. {
  508. EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex);
  509. m_SelectedKeyframeIndex = -1;
  510. SaveCurve(m_SelectedCurve, animCurve);
  511. }
  512. e.Use();
  513. }
  514. }
  515. }
  516. void SaveCurve(SerializedProperty prop, AnimationCurve curve)
  517. {
  518. prop.animationCurveValue = curve;
  519. }
  520. void Invalidate()
  521. {
  522. m_Dirty = true;
  523. }
  524. #endregion
  525. #region Keyframe manipulations
  526. void SelectKeyframe(SerializedProperty curve, int keyframeIndex)
  527. {
  528. m_SelectedKeyframeIndex = keyframeIndex;
  529. m_SelectedCurve = curve;
  530. Invalidate();
  531. }
  532. void ContextMenuAddKey(Vector3 hit, bool createOnCurve)
  533. {
  534. SerializedObject serializedObject = null;
  535. foreach (var curve in m_Curves)
  536. {
  537. if (!curve.Value.editable || !curve.Value.visible)
  538. continue;
  539. var prop = curve.Key;
  540. var state = curve.Value;
  541. if (serializedObject == null)
  542. {
  543. serializedObject = prop.serializedObject;
  544. serializedObject.Update();
  545. }
  546. var animCurve = prop.animationCurveValue;
  547. EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue);
  548. SaveCurve(prop, animCurve);
  549. }
  550. if (serializedObject != null)
  551. serializedObject.ApplyModifiedProperties();
  552. Invalidate();
  553. }
  554. void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue)
  555. {
  556. float tangent = EvaluateTangent(curve, position.x);
  557. if (createOnCurve)
  558. {
  559. position.y = curve.length == 0
  560. ? zeroKeyConstantValue
  561. : curve.Evaluate(position.x);
  562. }
  563. AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent));
  564. }
  565. void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex)
  566. {
  567. RemoveKeyframe(curve, keyframeIndex);
  568. }
  569. void AddKeyframe(AnimationCurve curve, Keyframe newValue)
  570. {
  571. curve.AddKey(newValue);
  572. Invalidate();
  573. }
  574. void RemoveKeyframe(AnimationCurve curve, int keyframeIndex)
  575. {
  576. curve.RemoveKey(keyframeIndex);
  577. Invalidate();
  578. }
  579. void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue)
  580. {
  581. var keys = curve.keys;
  582. if (keyframeIndex > 0)
  583. newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time);
  584. if (keyframeIndex < keys.Length - 1)
  585. newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time);
  586. curve.MoveKey(keyframeIndex, newValue);
  587. Invalidate();
  588. }
  589. void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex)
  590. {
  591. var key = CanvasToCurve(Event.current.mousePosition);
  592. float inTgt = keys[keyframeIndex].inTangent;
  593. float outTgt = keys[keyframeIndex].outTangent;
  594. SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt));
  595. }
  596. void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents)
  597. {
  598. var pos = CanvasToCurve(Event.current.mousePosition);
  599. float time = keys[keyframeIndex].time;
  600. float value = keys[keyframeIndex].value;
  601. pos -= new Vector3(time, value);
  602. if (targetTangent == Tangent.In && pos.x > 0f)
  603. pos.x = 0f;
  604. if (targetTangent == Tangent.Out && pos.x < 0f)
  605. pos.x = 0f;
  606. float tangent;
  607. if (Mathf.Approximately(pos.x, 0f))
  608. tangent = pos.y < 0f ? float.PositiveInfinity : float.NegativeInfinity;
  609. else
  610. tangent = pos.y / pos.x;
  611. float inTangent = keys[keyframeIndex].inTangent;
  612. float outTangent = keys[keyframeIndex].outTangent;
  613. if (targetTangent == Tangent.In || linkTangents)
  614. inTangent = tangent;
  615. if (targetTangent == Tangent.Out || linkTangents)
  616. outTangent = tangent;
  617. SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent));
  618. }
  619. #endregion
  620. #region Maths utilities
  621. Vector3 CurveToCanvas(Keyframe keyframe)
  622. {
  623. return CurveToCanvas(new Vector3(keyframe.time, keyframe.value));
  624. }
  625. Vector3 CurveToCanvas(Vector3 position)
  626. {
  627. var bounds = settings.bounds;
  628. var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y));
  629. output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin;
  630. output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin;
  631. return output;
  632. }
  633. Vector3 CanvasToCurve(Vector3 position)
  634. {
  635. var bounds = settings.bounds;
  636. var output = position;
  637. output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin);
  638. output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin);
  639. output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x);
  640. output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y);
  641. return output;
  642. }
  643. Vector3 CurveTangentToCanvas(float tangent)
  644. {
  645. if (!float.IsInfinity(tangent))
  646. {
  647. var bounds = settings.bounds;
  648. float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y));
  649. return new Vector3(1f, -tangent / ratio).normalized;
  650. }
  651. return float.IsPositiveInfinity(tangent) ? Vector3.up : Vector3.down;
  652. }
  653. Vector3[] BezierSegment(Keyframe start, Keyframe end)
  654. {
  655. var segment = new Vector3[4];
  656. segment[0] = CurveToCanvas(new Vector3(start.time, start.value));
  657. segment[3] = CurveToCanvas(new Vector3(end.time, end.value));
  658. float middle = start.time + ((end.time - start.time) * 0.333333f);
  659. float middle2 = start.time + ((end.time - start.time) * 0.666666f);
  660. segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle)));
  661. segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2)));
  662. return segment;
  663. }
  664. Vector3[] HardSegment(Keyframe start, Keyframe end)
  665. {
  666. var segment = new Vector3[3];
  667. segment[0] = CurveToCanvas(start);
  668. segment[1] = CurveToCanvas(new Vector3(end.time, start.value));
  669. segment[2] = CurveToCanvas(end);
  670. return segment;
  671. }
  672. float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition)
  673. {
  674. return inValue + ((projPosition - inPosition) * inTangent);
  675. }
  676. float EvaluateTangent(AnimationCurve curve, float time)
  677. {
  678. int prev = -1, next = 0;
  679. for (int i = 0; i < curve.keys.Length; i++)
  680. {
  681. if (time > curve.keys[i].time)
  682. {
  683. prev = i;
  684. next = i + 1;
  685. }
  686. else break;
  687. }
  688. if (next == 0)
  689. return 0f;
  690. if (prev == curve.keys.Length - 1)
  691. return 0f;
  692. const float kD = 1e-3f;
  693. float tp = Mathf.Max(time - kD, curve.keys[prev].time);
  694. float tn = Mathf.Min(time + kD, curve.keys[next].time);
  695. float vp = curve.Evaluate(tp);
  696. float vn = curve.Evaluate(tn);
  697. if (Mathf.Approximately(tn, tp))
  698. return (vn - vp > 0f) ? float.PositiveInfinity : float.NegativeInfinity;
  699. return (vn - vp) / (tn - tp);
  700. }
  701. #endregion
  702. }
  703. }