SceneCamera.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using BansheeEngine;
  4. namespace BansheeEditor
  5. {
  6. /** @addtogroup Scene-Editor
  7. * @{
  8. */
  9. /// <summary>
  10. /// Handles camera movement in the scene view.
  11. /// </summary>
  12. [RunInEditor]
  13. internal sealed class SceneCamera : ManagedComponent
  14. {
  15. #region Constants
  16. public const string MoveForwardBinding = "SceneForward";
  17. public const string MoveLeftBinding = "SceneLeft";
  18. public const string MoveRightBinding = "SceneRight";
  19. public const string MoveBackBinding = "SceneBackward";
  20. public const string MoveUpBinding = "SceneUp";
  21. public const string MoveDownBinding = "SceneDown";
  22. public const string FastMoveBinding = "SceneFastMove";
  23. public const string PanBinding = "ScenePan";
  24. public const string RotateBinding = "SceneRotate";
  25. public const string HorizontalAxisBinding = "SceneHorizontal";
  26. public const string VerticalAxisBinding = "SceneVertical";
  27. public const string ScrollAxisBinding = "SceneScroll";
  28. #endregion
  29. #region Fields
  30. private VirtualButton moveForwardBtn;
  31. private VirtualButton moveLeftBtn;
  32. private VirtualButton moveRightBtn;
  33. private VirtualButton moveBackwardBtn;
  34. private VirtualButton moveUpBtn;
  35. private VirtualButton moveDownBtn;
  36. private VirtualButton fastMoveBtn;
  37. private VirtualButton activeBtn;
  38. private VirtualButton panBtn;
  39. private VirtualAxis horizontalAxis;
  40. private VirtualAxis verticalAxis;
  41. private VirtualAxis scrollAxis;
  42. private float currentSpeed;
  43. private Degree yaw;
  44. private Degree pitch;
  45. private bool lastHideCursorState;
  46. private Camera camera;
  47. private bool inputEnabled = true;
  48. // Animating camera transitions
  49. private CameraAnimation animation = new CameraAnimation();
  50. private float frustumWidth = 50.0f;
  51. private float lerp;
  52. private bool isAnimating;
  53. #endregion
  54. #region Public properties
  55. /// <summary>
  56. /// Type of projection used by camera for rendering the scene.
  57. /// </summary>
  58. public ProjectionType ProjectionType
  59. {
  60. get { return camera.ProjectionType; }
  61. set
  62. {
  63. if (camera.ProjectionType != value)
  64. {
  65. CameraState state = new CameraState();
  66. state.Position = camera.SceneObject.Position;
  67. state.Rotation = camera.SceneObject.Rotation;
  68. state.Orthographic = value == ProjectionType.Orthographic;
  69. state.FrustumWidth = frustumWidth;
  70. SetState(state);
  71. }
  72. }
  73. }
  74. /// <summary>
  75. /// The orthographic size of the scene camera.
  76. /// </summary>
  77. public float OrthographicSize {
  78. get { return camera.OrthoHeight; }
  79. set {
  80. camera.OrthoHeight = value;
  81. SceneCameraOptions.SetOrthographicSize(value);
  82. }
  83. }
  84. /// <summary>
  85. /// The field of view of the scene camera.
  86. /// </summary>
  87. public Degree FieldOfView {
  88. get { return camera.FieldOfView; }
  89. set {
  90. camera.FieldOfView = value;
  91. SceneCameraOptions.SetFieldOfView(value.Degrees);
  92. }
  93. }
  94. /// <summary>
  95. /// The near clip plane of the scene camera.
  96. /// </summary>
  97. public float NearClipPlane {
  98. get { return camera.NearClipPlane; }
  99. set {
  100. camera.NearClipPlane = value;
  101. SceneCameraOptions.SetNearClipPlane(value);
  102. }
  103. }
  104. /// <summary>
  105. /// The far clip plane of the scene camera.
  106. /// </summary>
  107. public float FarClipPlane {
  108. get { return camera.FarClipPlane; }
  109. set {
  110. camera.FarClipPlane = value;
  111. SceneCameraOptions.SetFarClipPlane(value);
  112. }
  113. }
  114. /// <summary>
  115. /// The scroll speed of the scene camera.
  116. /// </summary>
  117. public float ScrollSpeed
  118. {
  119. get { return SceneCameraOptions.ScrollSpeed; }
  120. set { SceneCameraOptions.SetScrollSpeed(value); }
  121. }
  122. private SceneCameraOptions SceneCameraOptions { get; set; } = new SceneCameraOptions();
  123. #endregion
  124. #region Public methods
  125. /// <summary>
  126. /// Initializes default scene camera options.
  127. /// </summary>
  128. public void Initialize()
  129. {
  130. camera.NearClipPlane = SceneCameraOptions.NearClipPlane;
  131. camera.FarClipPlane = SceneCameraOptions.FarClipPlane;
  132. camera.OrthoHeight = SceneCameraOptions.OrthographicSize;
  133. camera.FieldOfView = SceneCameraOptions.FieldOfView;
  134. }
  135. /// <summary>
  136. /// Enables or disables camera controls.
  137. /// </summary>
  138. /// <param name="enable">True to enable controls, false to disable.</param>
  139. public void EnableInput(bool enable)
  140. {
  141. inputEnabled = enable;
  142. }
  143. /// <summary>
  144. /// Focuses the camera on the currently selected object(s).
  145. /// </summary>
  146. public void FrameSelected()
  147. {
  148. SceneObject[] selectedObjects = Selection.SceneObjects;
  149. if (selectedObjects.Length > 0)
  150. {
  151. AABox box = EditorUtility.CalculateBounds(Selection.SceneObjects);
  152. FrameBounds(box);
  153. }
  154. }
  155. /// <summary>
  156. /// Orients the camera so it looks along the provided axis.
  157. /// </summary>
  158. public void LookAlong(Vector3 axis)
  159. {
  160. Vector3 up = Vector3.YAxis;
  161. if (MathEx.Abs(Vector3.Dot(axis, up)) > 0.9f)
  162. up = Vector3.ZAxis;
  163. CameraState state = new CameraState();
  164. state.Position = camera.SceneObject.Position;
  165. state.Rotation = Quaternion.LookRotation(axis, up);
  166. state.Orthographic = camera.ProjectionType == ProjectionType.Orthographic;
  167. state.FrustumWidth = frustumWidth;
  168. SetState(state);
  169. }
  170. #endregion
  171. #region Private methods
  172. private void OnReset()
  173. {
  174. camera = SceneObject.GetComponent<Camera>();
  175. moveForwardBtn = new VirtualButton(MoveForwardBinding);
  176. moveLeftBtn = new VirtualButton(MoveLeftBinding);
  177. moveRightBtn = new VirtualButton(MoveRightBinding);
  178. moveBackwardBtn = new VirtualButton(MoveBackBinding);
  179. moveUpBtn = new VirtualButton(MoveUpBinding);
  180. moveDownBtn = new VirtualButton(MoveDownBinding);
  181. fastMoveBtn = new VirtualButton(FastMoveBinding);
  182. activeBtn = new VirtualButton(RotateBinding);
  183. panBtn = new VirtualButton(PanBinding);
  184. horizontalAxis = new VirtualAxis(HorizontalAxisBinding);
  185. verticalAxis = new VirtualAxis(VerticalAxisBinding);
  186. scrollAxis = new VirtualAxis(ScrollAxisBinding);
  187. }
  188. private void OnUpdate()
  189. {
  190. bool isOrthographic = camera.ProjectionType == ProjectionType.Orthographic;
  191. float frameDelta = Time.FrameDelta;
  192. if (inputEnabled)
  193. {
  194. bool goingForward = VirtualInput.IsButtonHeld(moveForwardBtn);
  195. bool goingBack = VirtualInput.IsButtonHeld(moveBackwardBtn);
  196. bool goingLeft = VirtualInput.IsButtonHeld(moveLeftBtn);
  197. bool goingRight = VirtualInput.IsButtonHeld(moveRightBtn);
  198. bool goingUp = VirtualInput.IsButtonHeld(moveUpBtn);
  199. bool goingDown = VirtualInput.IsButtonHeld(moveDownBtn);
  200. bool fastMove = VirtualInput.IsButtonHeld(fastMoveBtn);
  201. bool camActive = VirtualInput.IsButtonHeld(activeBtn);
  202. bool isPanning = VirtualInput.IsButtonHeld(panBtn);
  203. bool hideCursor = camActive || isPanning;
  204. if (hideCursor != lastHideCursorState)
  205. {
  206. if (hideCursor)
  207. {
  208. Cursor.Hide();
  209. Rect2I clipRect;
  210. clipRect.x = Input.PointerPosition.x - 2;
  211. clipRect.y = Input.PointerPosition.y - 2;
  212. clipRect.width = 4;
  213. clipRect.height = 4;
  214. Cursor.ClipToRect(clipRect);
  215. }
  216. else
  217. {
  218. Cursor.Show();
  219. Cursor.ClipDisable();
  220. }
  221. lastHideCursorState = hideCursor;
  222. }
  223. if (camActive)
  224. {
  225. float horzValue = VirtualInput.GetAxisValue(horizontalAxis);
  226. float vertValue = VirtualInput.GetAxisValue(verticalAxis);
  227. float rotationAmount = SceneCameraOptions.RotationalSpeed * EditorSettings.MouseSensitivity;
  228. yaw += new Degree(horzValue * rotationAmount);
  229. pitch += new Degree(vertValue * rotationAmount);
  230. yaw = MathEx.WrapAngle(yaw);
  231. pitch = MathEx.WrapAngle(pitch);
  232. Quaternion yRot = Quaternion.FromAxisAngle(Vector3.YAxis, yaw);
  233. Quaternion xRot = Quaternion.FromAxisAngle(Vector3.XAxis, pitch);
  234. Quaternion camRot = yRot * xRot;
  235. camRot.Normalize();
  236. SceneObject.Rotation = camRot;
  237. // Handle movement using movement keys
  238. Vector3 direction = Vector3.Zero;
  239. if (goingForward) direction += SceneObject.Forward;
  240. if (goingBack) direction -= SceneObject.Forward;
  241. if (goingRight) direction += SceneObject.Right;
  242. if (goingLeft) direction -= SceneObject.Right;
  243. if (goingUp) direction += SceneObject.Up;
  244. if (goingDown) direction -= SceneObject.Up;
  245. if (direction.SqrdLength != 0)
  246. {
  247. direction.Normalize();
  248. float multiplier = 1.0f;
  249. if (fastMove)
  250. multiplier = SceneCameraOptions.FastModeMultiplier;
  251. currentSpeed = MathEx.Clamp(currentSpeed + SceneCameraOptions.Acceleration * frameDelta, SceneCameraOptions.StartSpeed, SceneCameraOptions.TopSpeed);
  252. currentSpeed *= multiplier;
  253. }
  254. else
  255. {
  256. currentSpeed = 0.0f;
  257. }
  258. const float tooSmall = 0.0001f;
  259. if (currentSpeed > tooSmall)
  260. {
  261. Vector3 velocity = direction * currentSpeed;
  262. SceneObject.Move(velocity * frameDelta);
  263. }
  264. }
  265. // Pan
  266. if (isPanning)
  267. {
  268. float horzValue = VirtualInput.GetAxisValue(horizontalAxis);
  269. float vertValue = VirtualInput.GetAxisValue(verticalAxis);
  270. Vector3 direction = new Vector3(horzValue, -vertValue, 0.0f);
  271. direction = camera.SceneObject.Rotation.Rotate(direction);
  272. SceneObject.Move(direction * SceneCameraOptions.PanSpeed * frameDelta);
  273. }
  274. }
  275. else
  276. {
  277. if (lastHideCursorState)
  278. {
  279. Cursor.Show();
  280. Cursor.ClipDisable();
  281. lastHideCursorState = false;
  282. }
  283. }
  284. SceneWindow sceneWindow = EditorWindow.GetWindow<SceneWindow>();
  285. if ((sceneWindow.Active && sceneWindow.HasFocus) || sceneWindow.IsPointerHovering)
  286. {
  287. Rect2I bounds = sceneWindow.Bounds;
  288. // Move using scroll wheel
  289. if (bounds.Contains(Input.PointerPosition))
  290. {
  291. float scrollAmount = VirtualInput.GetAxisValue(scrollAxis);
  292. if(scrollAmount != 0)
  293. {
  294. if (!isOrthographic)
  295. {
  296. SceneObject.Move(SceneObject.Forward * scrollAmount * SceneCameraOptions.ScrollSpeed * frameDelta);
  297. }
  298. else
  299. {
  300. float orthoHeight = MathEx.Max(1.0f, OrthographicSize - scrollAmount * frameDelta);
  301. if (OrthographicSize != orthoHeight)
  302. {
  303. OrthographicSize = orthoHeight;
  304. }
  305. }
  306. }
  307. }
  308. }
  309. UpdateAnim();
  310. }
  311. /// <summary>
  312. /// Moves and orients a camera so that the provided bounds end covering the camera's viewport.
  313. /// </summary>
  314. /// <param name="bounds">Bounds to frame in camera's view.</param>
  315. /// <param name="padding">Amount of padding to leave on the borders of the viewport, in percent [0, 1].</param>
  316. private void FrameBounds(AABox bounds, float padding = 0.0f)
  317. {
  318. // TODO - Use AABox bounds directly instead of a sphere to be more accurate
  319. float worldWidth = bounds.Size.Length;
  320. float worldHeight = worldWidth;
  321. if (worldWidth == 0.0f)
  322. worldWidth = 1.0f;
  323. if (worldHeight == 0.0f)
  324. worldHeight = 1.0f;
  325. float boundsAspect = worldWidth / worldHeight;
  326. float paddingScale = MathEx.Clamp01(padding) + 1.0f;
  327. float frustumWidth;
  328. // If camera has wider aspect than bounds then height will be the limiting dimension
  329. if (camera.AspectRatio > boundsAspect)
  330. frustumWidth = worldHeight * camera.AspectRatio * paddingScale;
  331. else // Otherwise width
  332. frustumWidth = worldWidth * paddingScale;
  333. float distance = CalcDistanceForFrustumWidth(frustumWidth);
  334. Vector3 forward = bounds.Center - SceneObject.Position;
  335. forward.Normalize();
  336. CameraState state = new CameraState();
  337. state.Position = bounds.Center - forward * distance;
  338. state.Rotation = Quaternion.LookRotation(forward, Vector3.YAxis);
  339. state.Orthographic = camera.ProjectionType == ProjectionType.Orthographic;
  340. state.FrustumWidth = frustumWidth;
  341. SetState(state);
  342. }
  343. /// <summary>
  344. /// Changes the state of the camera, either instantly or animated over several frames. The state includes
  345. /// camera position, rotation, type and possibly other parameters.
  346. /// </summary>
  347. /// <param name="state">New state of the camera.</param>
  348. /// <param name="animated">Should the state be linearly interpolated over a course of several frames.</param>
  349. private void SetState(CameraState state, bool animated = true)
  350. {
  351. CameraState startState = new CameraState();
  352. startState.Position = SceneObject.Position;
  353. startState.Rotation = SceneObject.Rotation;
  354. startState.Orthographic = camera.ProjectionType == ProjectionType.Orthographic;
  355. startState.FrustumWidth = frustumWidth;
  356. animation.Start(startState, state);
  357. if (!animated)
  358. {
  359. ApplyState(1.0f);
  360. isAnimating = false;
  361. }
  362. else
  363. {
  364. isAnimating = true;
  365. lerp = 0.0f;
  366. }
  367. }
  368. /// <summary>
  369. /// Applies the animation target state depending on the interpolation parameter. <see cref="SetState"/>.
  370. /// </summary>
  371. /// <param name="t">Interpolation parameter ranging [0, 1] that interpolated between the start state and the
  372. /// target state.</param>
  373. private void ApplyState(float t)
  374. {
  375. animation.Update(t);
  376. SceneObject.Position = animation.State.Position;
  377. SceneObject.Rotation = animation.State.Rotation;
  378. frustumWidth = animation.State.FrustumWidth;
  379. Vector3 eulerAngles = SceneObject.Rotation.ToEuler();
  380. pitch = (Degree)eulerAngles.x;
  381. yaw = (Degree)eulerAngles.y;
  382. Degree FOV = (Degree)(1.0f - animation.State.OrthographicPct) * SceneCameraOptions.FieldOfView;
  383. if (FOV < (Degree)5.0f)
  384. {
  385. camera.ProjectionType = ProjectionType.Orthographic;
  386. camera.OrthoHeight = frustumWidth * 0.5f / camera.AspectRatio;
  387. }
  388. else
  389. {
  390. camera.ProjectionType = ProjectionType.Perspective;
  391. camera.FieldOfView = FOV;
  392. }
  393. // Note: Consider having a global setting for near/far planes as changing it here might confuse the user
  394. float distance = CalcDistanceForFrustumWidth(frustumWidth);
  395. if (distance < 1)
  396. {
  397. camera.NearClipPlane = 0.005f;
  398. camera.FarClipPlane = 1000f;
  399. }
  400. if (distance < 100)
  401. {
  402. camera.NearClipPlane = 0.05f;
  403. camera.FarClipPlane = 2500f;
  404. }
  405. else if (distance < 1000)
  406. {
  407. camera.NearClipPlane = 0.5f;
  408. camera.FarClipPlane = 10000f;
  409. }
  410. else
  411. {
  412. camera.NearClipPlane = 5.0f;
  413. camera.FarClipPlane = 1000000f;
  414. }
  415. }
  416. /// <summary>
  417. /// Calculates distance at which the camera's frustum width is equal to the provided width.
  418. /// </summary>
  419. /// <param name="frustumWidth">Frustum width to find the distance for, in world units.</param>
  420. /// <returns>Distance at which the camera's frustum is the specified width, in world units.</returns>
  421. private float CalcDistanceForFrustumWidth(float frustumWidth)
  422. {
  423. if (camera.ProjectionType == ProjectionType.Perspective)
  424. return (frustumWidth * 0.5f) / MathEx.Tan(camera.FieldOfView * 0.5f);
  425. else
  426. return frustumWidth * 2.0f;
  427. }
  428. /// <summary>
  429. /// Updates camera state transition animation. Should be called every frame.
  430. /// </summary>
  431. private void UpdateAnim()
  432. {
  433. if (!isAnimating)
  434. return;
  435. const float ANIM_TIME = 0.5f; // 0.5f seconds
  436. lerp += Time.FrameDelta * (1.0f / ANIM_TIME);
  437. if (lerp >= 1.0f)
  438. {
  439. lerp = 1.0f;
  440. isAnimating = false;
  441. }
  442. ApplyState(lerp);
  443. }
  444. /// <summary>
  445. /// Contains data for a possible camera state. Camera states can be interpolated between each other as needed.
  446. /// </summary>
  447. private struct CameraState
  448. {
  449. private float _orthographic;
  450. public Vector3 Position { get; set; }
  451. public Quaternion Rotation { get; set; }
  452. public float FrustumWidth { get; set; }
  453. public bool Orthographic
  454. {
  455. get { return _orthographic > 0.5; }
  456. set { _orthographic = value ? 1.0f : 0.0f; }
  457. }
  458. public float OrthographicPct
  459. {
  460. get { return _orthographic; }
  461. set { _orthographic = value; }
  462. }
  463. }
  464. /// <summary>
  465. /// Helper class that performs linear interpolation between two camera states.
  466. /// </summary>
  467. private struct CameraAnimation
  468. {
  469. private CameraState start;
  470. private CameraState target;
  471. private CameraState interpolated;
  472. /// <summary>
  473. /// Returns currently interpolated animation state.
  474. /// </summary>
  475. public CameraState State
  476. {
  477. get { return interpolated; }
  478. }
  479. /// <summary>
  480. /// Initializes the animation with initial and target states.
  481. /// </summary>
  482. /// <param name="start">Initial state to animate from.</param>
  483. /// <param name="target">Target state to animate towards.</param>
  484. public void Start(CameraState start, CameraState target)
  485. {
  486. this.start = start;
  487. this.target = target;
  488. }
  489. /// <summary>
  490. /// Updates the animation by interpolating between the start and target states.
  491. /// </summary>
  492. /// <param name="t">Interpolation parameter in range [0, 1] that determines how much to interpolate between
  493. /// start and target states.</param>
  494. public void Update(float t)
  495. {
  496. interpolated = new CameraState();
  497. interpolated.Position = start.Position * (1.0f - t) + target.Position * t;
  498. interpolated.Rotation = Quaternion.Slerp(start.Rotation, target.Rotation, t);
  499. interpolated.OrthographicPct = start.OrthographicPct * (1.0f - t) + target.OrthographicPct * t;
  500. interpolated.FrustumWidth = start.FrustumWidth * (1.0f - t) + target.FrustumWidth * t;
  501. }
  502. };
  503. #endregion
  504. }
  505. /** @} */
  506. }