CameraController.cs 20 KB

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