SceneCamera.cs 24 KB

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