CameraController.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. using System;
  2. using UnityEngine;
  3. using UnityEditor.ShortcutManagement;
  4. using UnityEngine.UIElements;
  5. namespace UnityEditor.Rendering.LookDev
  6. {
  7. class CameraController : Manipulator
  8. {
  9. [Flags]
  10. enum Direction
  11. {
  12. None = 0,
  13. Up = 1 << 0,
  14. Down = 1 << 1,
  15. Left = 1 << 2,
  16. Right = 1 << 3,
  17. Forward = 1 << 4,
  18. Backward = 1 << 5,
  19. All = Up | Down | Left | Right | Forward | Backward
  20. }
  21. Direction m_DirectionKeyPressed = Direction.None;
  22. float m_StartZoom = 0.0f;
  23. float m_ZoomSpeed = 0.0f;
  24. float m_TotalMotion = 0.0f;
  25. Vector3 m_MotionDirection = new Vector3();
  26. float m_FlySpeedNormalized = .5f;
  27. float m_FlySpeed = 1f;
  28. float m_FlySpeedAccelerated = 0f;
  29. const float m_FlySpeedMin = .01f;
  30. const float m_FlySpeedMax = 2f;
  31. //[TODO: check if necessary to add hability to deactivate acceleration]
  32. const float k_FlyAcceleration = 1.1f;
  33. bool m_ShiftBoostedFly = false;
  34. bool m_InFlyMotion;
  35. bool m_IsDragging;
  36. ViewTool m_BehaviorState;
  37. static TimeHelper s_Timer = new TimeHelper();
  38. protected CameraState m_CameraState;
  39. DisplayWindow m_Window;
  40. protected Action m_Focused;
  41. Rect screen => target.contentRect;
  42. bool inFlyMotion
  43. {
  44. get => m_InFlyMotion;
  45. set
  46. {
  47. if (value ^ m_InFlyMotion)
  48. {
  49. if (value)
  50. {
  51. s_Timer.Begin();
  52. EditorApplication.update += UpdateFPSMotion;
  53. }
  54. else
  55. {
  56. m_FlySpeedAccelerated = 0f;
  57. m_MotionDirection = Vector3.zero;
  58. m_ShiftBoostedFly = false;
  59. EditorApplication.update -= UpdateFPSMotion;
  60. }
  61. m_InFlyMotion = value;
  62. }
  63. }
  64. }
  65. float flySpeedNormalized
  66. {
  67. get => m_FlySpeedNormalized;
  68. set
  69. {
  70. m_FlySpeedNormalized = Mathf.Clamp01(value);
  71. float speed = Mathf.Lerp(m_FlySpeedMin, m_FlySpeedMax, m_FlySpeedNormalized);
  72. // Round to nearest decimal: 2 decimal points when between [0.01, 0.1]; 1 decimal point when between [0.1, 10]; integral between [10, 99]
  73. speed = (float)(System.Math.Round((double)speed, speed < 0.1f ? 2 : speed < 10f ? 1 : 0));
  74. m_FlySpeed = Mathf.Clamp(speed, m_FlySpeedMin, m_FlySpeedMax);
  75. }
  76. }
  77. float flySpeed
  78. {
  79. get => m_FlySpeed;
  80. set => flySpeedNormalized = Mathf.InverseLerp(m_FlySpeedMin, m_FlySpeedMax, value);
  81. }
  82. virtual protected bool isDragging
  83. {
  84. get => m_IsDragging;
  85. set
  86. {
  87. //As in scene view, stop dragging as first button is release in case of multiple button down
  88. if (value ^ m_IsDragging)
  89. {
  90. if (value)
  91. {
  92. target.RegisterCallback<MouseMoveEvent>(OnMouseDrag);
  93. target.CaptureMouse();
  94. EditorGUIUtility.SetWantsMouseJumping(1); //through screen edges
  95. }
  96. else
  97. {
  98. EditorGUIUtility.SetWantsMouseJumping(0);
  99. target.ReleaseMouse();
  100. target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag);
  101. }
  102. m_IsDragging = value;
  103. }
  104. }
  105. }
  106. public CameraController(CameraState cameraState, DisplayWindow window, Action focused)
  107. {
  108. m_CameraState = cameraState;
  109. m_Window = window;
  110. m_Focused = focused;
  111. }
  112. private void ResetCameraControl()
  113. {
  114. isDragging = false;
  115. inFlyMotion = false;
  116. m_BehaviorState = ViewTool.None;
  117. }
  118. protected virtual void OnScrollWheel(WheelEvent evt)
  119. {
  120. // See UnityEditor.SceneViewMotion.HandleScrollWheel
  121. switch (m_BehaviorState)
  122. {
  123. case ViewTool.FPS: OnChangeFPSCameraSpeed(evt); break;
  124. default: OnZoom(evt); break;
  125. }
  126. }
  127. void OnMouseDrag(MouseMoveEvent evt)
  128. {
  129. switch (m_BehaviorState)
  130. {
  131. case ViewTool.Orbit: OnMouseDragOrbit(evt); break;
  132. case ViewTool.FPS: OnMouseDragFPS(evt); break;
  133. case ViewTool.Pan: OnMouseDragPan(evt); break;
  134. case ViewTool.Zoom: OnMouseDragZoom(evt); break;
  135. default: break;
  136. }
  137. }
  138. void OnKeyDown(KeyDownEvent evt)
  139. {
  140. OnKeyUpOrDownFPS(evt);
  141. OnKeyDownReset(evt);
  142. }
  143. void OnChangeFPSCameraSpeed(WheelEvent evt)
  144. {
  145. float scrollWheelDelta = evt.delta.y;
  146. flySpeedNormalized -= scrollWheelDelta * .01f;
  147. string cameraSpeedDisplayValue = flySpeed.ToString(flySpeed < 0.1f ? "F2" : flySpeed < 10f ? "F1" : "F0");
  148. if (flySpeed < 0.1f)
  149. cameraSpeedDisplayValue = cameraSpeedDisplayValue.TrimStart(new Char[] { '0' });
  150. GUIContent cameraSpeedContent = EditorGUIUtility.TrTempContent(
  151. $"{cameraSpeedDisplayValue}x");
  152. m_Window.ShowNotification(cameraSpeedContent, .5f);
  153. evt.StopPropagation();
  154. }
  155. void OnZoom(WheelEvent evt)
  156. {
  157. const float deltaCutoff = .3f;
  158. const float minZoom = .003f;
  159. float scrollWheelDelta = evt.delta.y;
  160. float relativeDelta = m_CameraState.viewSize * scrollWheelDelta * .015f;
  161. if (relativeDelta > 0 && relativeDelta < deltaCutoff)
  162. relativeDelta = deltaCutoff;
  163. else if (relativeDelta <= 0 && relativeDelta > -deltaCutoff)
  164. relativeDelta = -deltaCutoff;
  165. m_CameraState.viewSize += relativeDelta;
  166. if (m_CameraState.viewSize < minZoom)
  167. m_CameraState.viewSize = minZoom;
  168. evt.StopPropagation();
  169. }
  170. void OnMouseDragOrbit(MouseMoveEvent evt)
  171. {
  172. Quaternion rotation = m_CameraState.rotation;
  173. rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation;
  174. rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation;
  175. m_CameraState.rotation = rotation;
  176. evt.StopPropagation();
  177. }
  178. void OnMouseDragFPS(MouseMoveEvent evt)
  179. {
  180. Vector3 camPos = m_CameraState.pivot - m_CameraState.rotation * Vector3.forward * m_CameraState.distanceFromPivot;
  181. Quaternion rotation = m_CameraState.rotation;
  182. rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation;
  183. rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation;
  184. m_CameraState.rotation = rotation;
  185. m_CameraState.pivot = camPos + rotation * Vector3.forward * m_CameraState.distanceFromPivot;
  186. evt.StopPropagation();
  187. }
  188. void OnMouseDragPan(MouseMoveEvent evt)
  189. {
  190. //[TODO: fix WorldToScreenPoint and ScreenToWorldPoint
  191. var screenPos = m_CameraState.QuickProjectPivotInScreen(screen);
  192. screenPos += new Vector3(evt.mouseDelta.x, -evt.mouseDelta.y, 0);
  193. //Vector3 newWorldPos = m_CameraState.ScreenToWorldPoint(screen, screenPos);
  194. Vector3 newWorldPos = m_CameraState.QuickReprojectionWithFixedFOVOnPivotPlane(screen, screenPos);
  195. Vector3 worldDelta = newWorldPos - m_CameraState.pivot;
  196. worldDelta *= EditorGUIUtility.pixelsPerPoint;
  197. if (evt.shiftKey)
  198. worldDelta *= 4;
  199. m_CameraState.pivot += worldDelta;
  200. evt.StopPropagation();
  201. }
  202. void OnMouseDragZoom(MouseMoveEvent evt)
  203. {
  204. float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shiftKey ? 9 : 3);
  205. m_TotalMotion += zoomDelta;
  206. if (m_TotalMotion < 0)
  207. m_CameraState.viewSize = m_StartZoom * (1 + m_TotalMotion * .001f);
  208. else
  209. m_CameraState.viewSize = m_CameraState.viewSize + zoomDelta * m_ZoomSpeed * .003f;
  210. evt.StopPropagation();
  211. }
  212. void OnKeyDownReset(KeyDownEvent evt)
  213. {
  214. if (evt.keyCode == KeyCode.Escape)
  215. ResetCameraControl();
  216. evt.StopPropagation();
  217. }
  218. void OnKeyUpOrDownFPS<T>(KeyboardEventBase<T> evt)
  219. where T : KeyboardEventBase<T>, new()
  220. {
  221. if (m_BehaviorState != ViewTool.FPS)
  222. return;
  223. //Note: Keydown is called in loop but between first occurence of the
  224. // loop and laters, there is a small pause. To deal with this, we
  225. // need to register the UpdateMovement function to the Editor update
  226. KeyCombination combination;
  227. if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt))
  228. RegisterMotionChange(Direction.Forward, evt);
  229. if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt))
  230. RegisterMotionChange(Direction.Backward, evt);
  231. if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt))
  232. RegisterMotionChange(Direction.Left, evt);
  233. if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt))
  234. RegisterMotionChange(Direction.Right, evt);
  235. if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt))
  236. RegisterMotionChange(Direction.Up, evt);
  237. if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt))
  238. RegisterMotionChange(Direction.Down, evt);
  239. }
  240. void RegisterMotionChange<T>(Direction direction, KeyboardEventBase<T> evt)
  241. where T : KeyboardEventBase<T>, new()
  242. {
  243. m_ShiftBoostedFly = evt.shiftKey;
  244. Direction formerDirection = m_DirectionKeyPressed;
  245. bool keyUp = evt is KeyUpEvent;
  246. bool keyDown = evt is KeyDownEvent;
  247. if (keyDown)
  248. m_DirectionKeyPressed |= direction;
  249. else if (keyUp)
  250. m_DirectionKeyPressed &= (Direction.All & ~direction);
  251. if (formerDirection != m_DirectionKeyPressed)
  252. {
  253. m_MotionDirection = new Vector3(
  254. ((m_DirectionKeyPressed & Direction.Right) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Left) > 0 ? 1 : 0),
  255. ((m_DirectionKeyPressed & Direction.Up) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Down) > 0 ? 1 : 0),
  256. ((m_DirectionKeyPressed & Direction.Forward) > 0 ? 1 : 0) - ((m_DirectionKeyPressed & Direction.Backward) > 0 ? 1 : 0));
  257. inFlyMotion = m_DirectionKeyPressed != Direction.None;
  258. }
  259. evt.StopPropagation();
  260. }
  261. Vector3 GetMotionDirection()
  262. {
  263. var deltaTime = s_Timer.Update();
  264. Vector3 result;
  265. float speed = (m_ShiftBoostedFly ? 5 * flySpeed : flySpeed);
  266. if (m_FlySpeedAccelerated == 0)
  267. m_FlySpeedAccelerated = 9;
  268. else
  269. m_FlySpeedAccelerated *= Mathf.Pow(k_FlyAcceleration, deltaTime);
  270. result = m_MotionDirection.normalized * m_FlySpeedAccelerated * speed * deltaTime;
  271. return result;
  272. }
  273. void UpdateFPSMotion()
  274. {
  275. m_CameraState.pivot += m_CameraState.rotation * GetMotionDirection();
  276. m_Window.Repaint(); //this prevent hich on key down as in CameraFlyModeContext.cs
  277. }
  278. bool GetKeyCombinationByID(string ID, out KeyCombination combination)
  279. {
  280. var sequence = ShortcutManager.instance.GetShortcutBinding(ID).keyCombinationSequence.GetEnumerator();
  281. if (sequence.MoveNext()) //have a first entry
  282. {
  283. combination = new KeyCombination(sequence.Current);
  284. return true;
  285. }
  286. else
  287. {
  288. combination = default;
  289. return false;
  290. }
  291. }
  292. void OnMouseUp(MouseUpEvent evt)
  293. {
  294. ResetCameraControl();
  295. evt.StopPropagation();
  296. }
  297. void OnMouseDown(MouseDownEvent evt)
  298. {
  299. bool onMac = Application.platform == RuntimePlatform.OSXEditor;
  300. if (evt.button == 2)
  301. m_BehaviorState = ViewTool.Pan;
  302. else if (evt.button == 0 && evt.ctrlKey && onMac || evt.button == 1 && evt.altKey)
  303. {
  304. m_BehaviorState = ViewTool.Zoom;
  305. m_StartZoom = m_CameraState.viewSize;
  306. m_ZoomSpeed = Mathf.Max(Mathf.Abs(m_StartZoom), .3f);
  307. m_TotalMotion = 0;
  308. }
  309. else if (evt.button == 0)
  310. m_BehaviorState = ViewTool.Orbit;
  311. else if (evt.button == 1 && !evt.altKey)
  312. m_BehaviorState = ViewTool.FPS;
  313. // see also SceneView.HandleClickAndDragToFocus()
  314. if (evt.button == 1 && onMac)
  315. m_Window.Focus();
  316. target.Focus(); //required for keyboard event
  317. isDragging = true;
  318. evt.StopPropagation();
  319. m_Focused?.Invoke();
  320. }
  321. protected override void RegisterCallbacksOnTarget()
  322. {
  323. target.focusable = true; //prerequisite for being focusable and recerive keydown events
  324. target.RegisterCallback<MouseUpEvent>(OnMouseUp);
  325. target.RegisterCallback<MouseDownEvent>(OnMouseDown);
  326. target.RegisterCallback<WheelEvent>(OnScrollWheel);
  327. target.RegisterCallback<KeyDownEvent>(OnKeyDown);
  328. target.RegisterCallback<KeyUpEvent>(OnKeyUpOrDownFPS);
  329. }
  330. protected override void UnregisterCallbacksFromTarget()
  331. {
  332. target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
  333. target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
  334. target.UnregisterCallback<WheelEvent>(OnScrollWheel);
  335. target.UnregisterCallback<KeyDownEvent>(OnKeyDown);
  336. target.UnregisterCallback<KeyUpEvent>(OnKeyUpOrDownFPS);
  337. }
  338. struct KeyCombination
  339. {
  340. KeyCode key;
  341. EventModifiers modifier;
  342. public bool shiftOnLastMatch;
  343. public KeyCombination(UnityEditor.ShortcutManagement.KeyCombination shortcutCombination)
  344. {
  345. key = shortcutCombination.keyCode;
  346. modifier = EventModifiers.None;
  347. if ((shortcutCombination.modifiers & ShortcutModifiers.Shift) != 0)
  348. modifier |= EventModifiers.Shift;
  349. if ((shortcutCombination.modifiers & ShortcutModifiers.Alt) != 0)
  350. modifier |= EventModifiers.Alt;
  351. if ((shortcutCombination.modifiers & ShortcutModifiers.Action) != 0)
  352. {
  353. if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer)
  354. modifier |= EventModifiers.Command;
  355. else
  356. modifier |= EventModifiers.Control;
  357. }
  358. shiftOnLastMatch = false;
  359. }
  360. //atLeastModifier allow case were A is required but event provide shift+A
  361. public bool Match(IKeyboardEvent evt, bool atLeastForModifier = true)
  362. {
  363. shiftOnLastMatch = evt.shiftKey;
  364. if (atLeastForModifier)
  365. return key == evt.keyCode && modifier == (evt.modifiers & modifier);
  366. else
  367. return key == evt.keyCode && modifier == evt.modifiers;
  368. }
  369. }
  370. struct TimeHelper
  371. {
  372. long lastTime;
  373. public void Begin() => lastTime = System.DateTime.Now.Ticks;
  374. public float Update()
  375. {
  376. float deltaTime = (System.DateTime.Now.Ticks - lastTime) / 10000000.0f;
  377. lastTime = System.DateTime.Now.Ticks;
  378. return deltaTime;
  379. }
  380. }
  381. }
  382. class SwitchableCameraController : CameraController
  383. {
  384. CameraState m_FirstView;
  385. CameraState m_SecondView;
  386. ViewIndex m_CurrentViewIndex;
  387. bool switchedDrag = false;
  388. bool switchedWheel = false;
  389. public SwitchableCameraController(CameraState cameraStateFirstView, CameraState cameraStateSecondView, DisplayWindow window, Action<ViewIndex> focused)
  390. : base(cameraStateFirstView, window, null)
  391. {
  392. m_FirstView = cameraStateFirstView;
  393. m_SecondView = cameraStateSecondView;
  394. m_CurrentViewIndex = ViewIndex.First;
  395. m_Focused = () => focused?.Invoke(m_CurrentViewIndex);
  396. }
  397. void SwitchTo(ViewIndex index)
  398. {
  399. CameraState stateToSwitch;
  400. switch (index)
  401. {
  402. case ViewIndex.First:
  403. stateToSwitch = m_FirstView;
  404. break;
  405. case ViewIndex.Second:
  406. stateToSwitch = m_SecondView;
  407. break;
  408. default:
  409. throw new ArgumentException("Unknown ViewIndex");
  410. }
  411. if (stateToSwitch != m_CameraState)
  412. m_CameraState = stateToSwitch;
  413. m_CurrentViewIndex = index;
  414. }
  415. public void SwitchUntilNextEndOfDrag()
  416. {
  417. switchedDrag = true;
  418. SwitchTo(ViewIndex.Second);
  419. }
  420. override protected bool isDragging
  421. {
  422. get => base.isDragging;
  423. set
  424. {
  425. bool switchBack = false;
  426. if (switchedDrag && base.isDragging && !value)
  427. switchBack = true;
  428. base.isDragging = value;
  429. if (switchBack)
  430. SwitchTo(ViewIndex.First);
  431. }
  432. }
  433. public void SwitchUntilNextWheelEvent()
  434. {
  435. switchedWheel = true;
  436. SwitchTo(ViewIndex.Second);
  437. }
  438. protected override void OnScrollWheel(WheelEvent evt)
  439. {
  440. base.OnScrollWheel(evt);
  441. if (switchedWheel)
  442. SwitchTo(ViewIndex.First);
  443. }
  444. }
  445. }