CinemachineComposer.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. using UnityEngine;
  2. using System;
  3. using Cinemachine.Utility;
  4. using UnityEngine.Serialization;
  5. namespace Cinemachine
  6. {
  7. /// <summary>
  8. /// This is a CinemachineComponent in the Aim section of the component pipeline.
  9. /// Its job is to aim the camera at the vcam's LookAt target object, with
  10. /// configurable offsets, damping, and composition rules.
  11. ///
  12. /// The composer does not change the camera's position. It will only pan and tilt the
  13. /// camera where it is, in order to get the desired framing. To move the camera, you have
  14. /// to use the virtual camera's Body section.
  15. /// </summary>
  16. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  17. [AddComponentMenu("")] // Don't display in add component menu
  18. [SaveDuringPlay]
  19. public class CinemachineComposer : CinemachineComponentBase
  20. {
  21. /// <summary>Target offset from the object's center in LOCAL space which
  22. /// the Composer tracks. Use this to fine-tune the tracking target position
  23. /// when the desired area is not in the tracked object's center</summary>
  24. [Tooltip("Target offset from the target object's center in target-local space. Use this to "
  25. + "fine-tune the tracking target position when the desired area is not the tracked object's center.")]
  26. public Vector3 m_TrackedObjectOffset = Vector3.zero;
  27. /// <summary>This setting will instruct the composer to adjust its target offset based
  28. /// on the motion of the target. The composer will look at a point where it estimates
  29. /// the target will be this many seconds into the future. Note that this setting is sensitive
  30. /// to noisy animation, and can amplify the noise, resulting in undesirable camera jitter.
  31. /// If the camera jitters unacceptably when the target is in motion, turn down this setting,
  32. /// or animate the target more smoothly.</summary>
  33. [Space]
  34. [Tooltip("This setting will instruct the composer to adjust its target offset based on the motion "
  35. + "of the target. The composer will look at a point where it estimates the target will be this "
  36. + "many seconds into the future. Note that this setting is sensitive to noisy animation, and "
  37. + "can amplify the noise, resulting in undesirable camera jitter. If the camera jitters "
  38. + "unacceptably when the target is in motion, turn down this setting, or animate the target more smoothly.")]
  39. [Range(0f, 1f)]
  40. public float m_LookaheadTime = 0;
  41. /// <summary>Controls the smoothness of the lookahead algorithm. Larger values smooth out
  42. /// jittery predictions and also increase prediction lag</summary>
  43. [Tooltip("Controls the smoothness of the lookahead algorithm. Larger values smooth "
  44. + "out jittery predictions and also increase prediction lag")]
  45. [Range(0, 30)]
  46. public float m_LookaheadSmoothing = 0;
  47. /// <summary>If checked, movement along the Y axis will be ignored for lookahead calculations</summary>
  48. [Tooltip("If checked, movement along the Y axis will be ignored for lookahead calculations")]
  49. public bool m_LookaheadIgnoreY;
  50. /// <summary>How aggressively the camera tries to follow the target in the screen-horizontal direction.
  51. /// Small numbers are more responsive, rapidly orienting the camera to keep the target in
  52. /// the dead zone. Larger numbers give a more heavy slowly responding camera.
  53. /// Using different vertical and horizontal settings can yield a wide range of camera behaviors.</summary>
  54. [Space]
  55. [Range(0f, 20)]
  56. [Tooltip("How aggressively the camera tries to follow the target in the screen-horizontal direction. "
  57. + "Small numbers are more responsive, rapidly orienting the camera to keep the target in "
  58. + "the dead zone. Larger numbers give a more heavy slowly responding camera. Using different "
  59. + "vertical and horizontal settings can yield a wide range of camera behaviors.")]
  60. public float m_HorizontalDamping = 0.5f;
  61. /// <summary>How aggressively the camera tries to follow the target in the screen-vertical direction.
  62. /// Small numbers are more responsive, rapidly orienting the camera to keep the target in
  63. /// the dead zone. Larger numbers give a more heavy slowly responding camera. Using different vertical
  64. /// and horizontal settings can yield a wide range of camera behaviors.</summary>
  65. [Range(0f, 20)]
  66. [Tooltip("How aggressively the camera tries to follow the target in the screen-vertical direction. "
  67. + "Small numbers are more responsive, rapidly orienting the camera to keep the target in "
  68. + "the dead zone. Larger numbers give a more heavy slowly responding camera. Using different "
  69. + "vertical and horizontal settings can yield a wide range of camera behaviors.")]
  70. public float m_VerticalDamping = 0.5f;
  71. /// <summary>Horizontal screen position for target. The camera will rotate to the position the tracked object here</summary>
  72. [Space]
  73. [Range(-0.5f, 1.5f)]
  74. [Tooltip("Horizontal screen position for target. The camera will rotate to position the tracked object here.")]
  75. public float m_ScreenX = 0.5f;
  76. /// <summary>Vertical screen position for target, The camera will rotate to to position the tracked object here</summary>
  77. [Range(-0.5f, 1.5f)]
  78. [Tooltip("Vertical screen position for target, The camera will rotate to position the tracked object here.")]
  79. public float m_ScreenY = 0.5f;
  80. /// <summary>Camera will not rotate horizontally if the target is within this range of the position</summary>
  81. [Range(0f, 2f)]
  82. [Tooltip("Camera will not rotate horizontally if the target is within this range of the position.")]
  83. public float m_DeadZoneWidth = 0f;
  84. /// <summary>Camera will not rotate vertically if the target is within this range of the position</summary>
  85. [Range(0f, 2f)]
  86. [Tooltip("Camera will not rotate vertically if the target is within this range of the position.")]
  87. public float m_DeadZoneHeight = 0f;
  88. /// <summary>When target is within this region, camera will gradually move to re-align
  89. /// towards the desired position, depending onm the damping speed</summary>
  90. [Range(0f, 2f)]
  91. [Tooltip("When target is within this region, camera will gradually rotate horizontally to re-align "
  92. + "towards the desired position, depending on the damping speed.")]
  93. public float m_SoftZoneWidth = 0.8f;
  94. /// <summary>When target is within this region, camera will gradually move to re-align
  95. /// towards the desired position, depending onm the damping speed</summary>
  96. [Range(0f, 2f)]
  97. [Tooltip("When target is within this region, camera will gradually rotate vertically to re-align "
  98. + "towards the desired position, depending on the damping speed.")]
  99. public float m_SoftZoneHeight = 0.8f;
  100. /// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
  101. [Range(-0.5f, 0.5f)]
  102. [Tooltip("A non-zero bias will move the target position horizontally away from the center of the soft zone.")]
  103. public float m_BiasX = 0f;
  104. /// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
  105. [Range(-0.5f, 0.5f)]
  106. [Tooltip("A non-zero bias will move the target position vertically away from the center of the soft zone.")]
  107. public float m_BiasY = 0f;
  108. /// <summary>Force target to center of screen when this camera activates.
  109. /// If false, will clamp target to the edges of the dead zone</summary>
  110. [Tooltip("Force target to center of screen when this camera activates. If false, will "
  111. + "clamp target to the edges of the dead zone")]
  112. public bool m_CenterOnActivate = true;
  113. /// <summary>True if component is enabled and has a LookAt defined</summary>
  114. public override bool IsValid { get { return enabled && LookAtTarget != null; } }
  115. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  116. /// Always returns the Aim stage</summary>
  117. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Aim; } }
  118. /// <summary>Internal API for inspector</summary>
  119. public Vector3 TrackedPoint { get; private set; }
  120. /// <summary>Apply the target offsets to the target location.
  121. /// Also set the TrackedPoint property, taking lookahead into account.</summary>
  122. /// <param name="lookAt">The unoffset LookAt point</param>
  123. /// <param name="up">Currest effective world up</param>
  124. /// <param name="deltaTime">Current effective deltaTime</param>
  125. /// <returns>The LookAt point with the offset applied</returns>
  126. protected virtual Vector3 GetLookAtPointAndSetTrackedPoint(
  127. Vector3 lookAt, Vector3 up, float deltaTime)
  128. {
  129. Vector3 pos = lookAt;
  130. if (LookAtTarget != null)
  131. pos += LookAtTargetRotation * m_TrackedObjectOffset;
  132. if (m_LookaheadTime < Epsilon)
  133. TrackedPoint = pos;
  134. else
  135. {
  136. var resetLookahead = VirtualCamera.LookAtTargetChanged || !VirtualCamera.PreviousStateIsValid;
  137. m_Predictor.Smoothing = m_LookaheadSmoothing;
  138. m_Predictor.AddPosition(pos, resetLookahead ? -1 : deltaTime, m_LookaheadTime);
  139. var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime);
  140. if (m_LookaheadIgnoreY)
  141. delta = delta.ProjectOntoPlane(up);
  142. TrackedPoint = pos + delta;
  143. }
  144. return pos;
  145. }
  146. /// <summary>State information for damping</summary>
  147. Vector3 m_CameraPosPrevFrame = Vector3.zero;
  148. Vector3 m_LookAtPrevFrame = Vector3.zero;
  149. Vector2 m_ScreenOffsetPrevFrame = Vector2.zero;
  150. Quaternion m_CameraOrientationPrevFrame = Quaternion.identity;
  151. internal PositionPredictor m_Predictor = new PositionPredictor();
  152. /// <summary>This is called to notify the us that a target got warped,
  153. /// so that we can update its internal state to make the camera
  154. /// also warp seamlessy.</summary>
  155. /// <param name="target">The object that was warped</param>
  156. /// <param name="positionDelta">The amount the target's position changed</param>
  157. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  158. {
  159. base.OnTargetObjectWarped(target, positionDelta);
  160. if (target == LookAtTarget)
  161. {
  162. m_CameraPosPrevFrame += positionDelta;
  163. m_LookAtPrevFrame += positionDelta;
  164. m_Predictor.ApplyTransformDelta(positionDelta);
  165. }
  166. }
  167. /// <summary>
  168. /// Force the virtual camera to assume a given position and orientation
  169. /// </summary>
  170. /// <param name="pos">Worldspace pposition to take</param>
  171. /// <param name="rot">Worldspace orientation to take</param>
  172. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  173. {
  174. base.ForceCameraPosition(pos, rot);
  175. m_CameraPosPrevFrame = pos;
  176. m_CameraOrientationPrevFrame = rot;
  177. }
  178. /// <summary>
  179. /// Report maximum damping time needed for this component.
  180. /// </summary>
  181. /// <returns>Highest damping setting in this component</returns>
  182. public override float GetMaxDampTime()
  183. {
  184. return Mathf.Max(m_HorizontalDamping, m_VerticalDamping);
  185. }
  186. /// <summary>Sets the state's ReferenceLookAt, applying the offset.</summary>
  187. /// <param name="curState">Input state that must be mutated</param>
  188. /// <param name="deltaTime">Current effective deltaTime</param>
  189. public override void PrePipelineMutateCameraState(ref CameraState curState, float deltaTime)
  190. {
  191. if (IsValid && curState.HasLookAt)
  192. curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(
  193. curState.ReferenceLookAt, curState.ReferenceUp, deltaTime);
  194. }
  195. /// <summary>Applies the composer rules and orients the camera accordingly</summary>
  196. /// <param name="curState">The current camera state</param>
  197. /// <param name="deltaTime">Used for calculating damping. If less than
  198. /// zero, then target will snap to the center of the dead zone.</param>
  199. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  200. {
  201. if (!IsValid || !curState.HasLookAt)
  202. return;
  203. // Correct the tracked point in the event that it's behind the camera
  204. // while the real target is in front
  205. if (!(TrackedPoint - curState.ReferenceLookAt).AlmostZero())
  206. {
  207. Vector3 mid = Vector3.Lerp(curState.CorrectedPosition, curState.ReferenceLookAt, 0.5f);
  208. Vector3 toLookAt = curState.ReferenceLookAt - mid;
  209. Vector3 toTracked = TrackedPoint - mid;
  210. if (Vector3.Dot(toLookAt, toTracked) < 0)
  211. {
  212. float t = Vector3.Distance(curState.ReferenceLookAt, mid)
  213. / Vector3.Distance(curState.ReferenceLookAt, TrackedPoint);
  214. TrackedPoint = Vector3.Lerp(curState.ReferenceLookAt, TrackedPoint, t);
  215. }
  216. }
  217. float targetDistance = (TrackedPoint - curState.CorrectedPosition).magnitude;
  218. if (targetDistance < Epsilon)
  219. {
  220. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  221. curState.RawOrientation = m_CameraOrientationPrevFrame;
  222. return; // navel-gazing, get outa here
  223. }
  224. // Expensive FOV calculations
  225. mCache.UpdateCache(curState.Lens, SoftGuideRect, HardGuideRect, targetDistance);
  226. Quaternion rigOrientation = curState.RawOrientation;
  227. if (deltaTime < 0 || !VirtualCamera.PreviousStateIsValid)
  228. {
  229. // No damping, just snap to central bounds, skipping the soft zone
  230. rigOrientation = Quaternion.LookRotation(
  231. rigOrientation * Vector3.forward, curState.ReferenceUp);
  232. Rect rect = mCache.mFovSoftGuideRect;
  233. if (m_CenterOnActivate)
  234. rect = new Rect(rect.center, Vector2.zero); // Force to center
  235. RotateToScreenBounds(
  236. ref curState, rect, curState.ReferenceLookAt,
  237. ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
  238. }
  239. else
  240. {
  241. // Start with previous frame's orientation (but with current up)
  242. Vector3 dir = m_LookAtPrevFrame - m_CameraPosPrevFrame;
  243. if (dir.AlmostZero())
  244. rigOrientation = Quaternion.LookRotation(
  245. m_CameraOrientationPrevFrame * Vector3.forward, curState.ReferenceUp);
  246. else
  247. {
  248. dir = Quaternion.Euler(curState.PositionDampingBypass) * dir;
  249. rigOrientation = Quaternion.LookRotation(dir, curState.ReferenceUp);
  250. rigOrientation = rigOrientation.ApplyCameraRotation(
  251. -m_ScreenOffsetPrevFrame, curState.ReferenceUp);
  252. }
  253. // Move target through the soft zone, with damping
  254. RotateToScreenBounds(
  255. ref curState, mCache.mFovSoftGuideRect, TrackedPoint,
  256. ref rigOrientation, mCache.mFov, mCache.mFovH, deltaTime);
  257. // Force the actual target (not the lookahead one) into the hard bounds, no damping
  258. if (deltaTime < 0 || VirtualCamera.LookAtTargetAttachment > 1 - Epsilon)
  259. RotateToScreenBounds(
  260. ref curState, mCache.mFovHardGuideRect, curState.ReferenceLookAt,
  261. ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
  262. }
  263. m_CameraPosPrevFrame = curState.CorrectedPosition;
  264. m_LookAtPrevFrame = TrackedPoint;
  265. m_CameraOrientationPrevFrame = UnityQuaternionExtensions.Normalized(rigOrientation);
  266. m_ScreenOffsetPrevFrame = m_CameraOrientationPrevFrame.GetCameraRotationToTarget(
  267. m_LookAtPrevFrame - curState.CorrectedPosition, curState.ReferenceUp);
  268. curState.RawOrientation = m_CameraOrientationPrevFrame;
  269. }
  270. /// <summary>Internal API for the inspector editor</summary>
  271. internal Rect SoftGuideRect
  272. {
  273. get
  274. {
  275. return new Rect(
  276. m_ScreenX - m_DeadZoneWidth / 2, m_ScreenY - m_DeadZoneHeight / 2,
  277. m_DeadZoneWidth, m_DeadZoneHeight);
  278. }
  279. set
  280. {
  281. m_DeadZoneWidth = Mathf.Clamp(value.width, 0, 2);
  282. m_DeadZoneHeight = Mathf.Clamp(value.height, 0, 2);
  283. m_ScreenX = Mathf.Clamp(value.x + m_DeadZoneWidth / 2, -0.5f, 1.5f);
  284. m_ScreenY = Mathf.Clamp(value.y + m_DeadZoneHeight / 2, -0.5f, 1.5f);
  285. m_SoftZoneWidth = Mathf.Max(m_SoftZoneWidth, m_DeadZoneWidth);
  286. m_SoftZoneHeight = Mathf.Max(m_SoftZoneHeight, m_DeadZoneHeight);
  287. }
  288. }
  289. /// <summary>Internal API for the inspector editor</summary>
  290. internal Rect HardGuideRect
  291. {
  292. get
  293. {
  294. Rect r = new Rect(
  295. m_ScreenX - m_SoftZoneWidth / 2, m_ScreenY - m_SoftZoneHeight / 2,
  296. m_SoftZoneWidth, m_SoftZoneHeight);
  297. r.position += new Vector2(
  298. m_BiasX * (m_SoftZoneWidth - m_DeadZoneWidth),
  299. m_BiasY * (m_SoftZoneHeight - m_DeadZoneHeight));
  300. return r;
  301. }
  302. set
  303. {
  304. m_SoftZoneWidth = Mathf.Clamp(value.width, 0, 2f);
  305. m_SoftZoneHeight = Mathf.Clamp(value.height, 0, 2f);
  306. m_DeadZoneWidth = Mathf.Min(m_DeadZoneWidth, m_SoftZoneWidth);
  307. m_DeadZoneHeight = Mathf.Min(m_DeadZoneHeight, m_SoftZoneHeight);
  308. }
  309. }
  310. // Cache for some expensive calculations
  311. struct FovCache
  312. {
  313. public Rect mFovSoftGuideRect;
  314. public Rect mFovHardGuideRect;
  315. public float mFovH;
  316. public float mFov;
  317. float mOrthoSizeOverDistance;
  318. float mAspect;
  319. Rect mSoftGuideRect;
  320. Rect mHardGuideRect;
  321. public void UpdateCache(
  322. LensSettings lens, Rect softGuide, Rect hardGuide, float targetDistance)
  323. {
  324. bool recalculate = mAspect != lens.Aspect
  325. || softGuide != mSoftGuideRect || hardGuide != mHardGuideRect;
  326. if (lens.Orthographic)
  327. {
  328. float orthoOverDistance = Mathf.Abs(lens.OrthographicSize / targetDistance);
  329. if (mOrthoSizeOverDistance == 0
  330. || Mathf.Abs(orthoOverDistance - mOrthoSizeOverDistance) / mOrthoSizeOverDistance
  331. > mOrthoSizeOverDistance * 0.01f)
  332. recalculate = true;
  333. if (recalculate)
  334. {
  335. // Calculate effective fov - fake it for ortho based on target distance
  336. mFov = Mathf.Rad2Deg * 2 * Mathf.Atan(orthoOverDistance);
  337. mFovH = Mathf.Rad2Deg * 2 * Mathf.Atan(lens.Aspect * orthoOverDistance);
  338. mOrthoSizeOverDistance = orthoOverDistance;
  339. }
  340. }
  341. else
  342. {
  343. var verticalFOV = lens.FieldOfView;
  344. if (mFov != verticalFOV)
  345. recalculate = true;
  346. if (recalculate)
  347. {
  348. mFov = verticalFOV;
  349. double radHFOV = 2 * Math.Atan(Math.Tan(mFov * Mathf.Deg2Rad / 2) * lens.Aspect);
  350. mFovH = (float)(Mathf.Rad2Deg * radHFOV);
  351. mOrthoSizeOverDistance = 0;
  352. }
  353. }
  354. if (recalculate)
  355. {
  356. mFovSoftGuideRect = ScreenToFOV(softGuide, mFov, mFovH, lens.Aspect);
  357. mSoftGuideRect = softGuide;
  358. mFovHardGuideRect = ScreenToFOV(hardGuide, mFov, mFovH, lens.Aspect);
  359. mHardGuideRect = hardGuide;
  360. mAspect = lens.Aspect;
  361. }
  362. }
  363. // Convert from screen coords to normalized FOV angular coords
  364. private Rect ScreenToFOV(Rect rScreen, float fov, float fovH, float aspect)
  365. {
  366. Rect r = new Rect(rScreen);
  367. Matrix4x4 persp = Matrix4x4.Perspective(fov, aspect, 0.0001f, 2f).inverse;
  368. Vector3 p = persp.MultiplyPoint(new Vector3(0, (r.yMin * 2f) - 1f, 0.5f)); p.z = -p.z;
  369. float angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
  370. r.yMin = ((fov / 2) + angle) / fov;
  371. p = persp.MultiplyPoint(new Vector3(0, (r.yMax * 2f) - 1f, 0.5f)); p.z = -p.z;
  372. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
  373. r.yMax = ((fov / 2) + angle) / fov;
  374. p = persp.MultiplyPoint(new Vector3((r.xMin * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
  375. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
  376. r.xMin = ((fovH / 2) + angle) / fovH;
  377. p = persp.MultiplyPoint(new Vector3((r.xMax * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
  378. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
  379. r.xMax = ((fovH / 2) + angle) / fovH;
  380. return r;
  381. }
  382. }
  383. FovCache mCache;
  384. /// <summary>
  385. /// Adjust the rigOrientation to put the camera within the screen bounds.
  386. /// If deltaTime >= 0 then damping will be applied.
  387. /// Assumes that currentOrientation fwd is such that input rigOrientation's
  388. /// local up is NEVER NEVER NEVER pointing downwards, relative to
  389. /// state.ReferenceUp. If this condition is violated
  390. /// then you will see crazy spinning. That's the symptom.
  391. /// </summary>
  392. private void RotateToScreenBounds(
  393. ref CameraState state, Rect screenRect, Vector3 trackedPoint,
  394. ref Quaternion rigOrientation, float fov, float fovH, float deltaTime)
  395. {
  396. Vector3 targetDir = trackedPoint - state.CorrectedPosition;
  397. Vector2 rotToRect = rigOrientation.GetCameraRotationToTarget(targetDir, state.ReferenceUp);
  398. // Bring it to the edge of screenRect, if outside. Leave it alone if inside.
  399. ClampVerticalBounds(ref screenRect, targetDir, state.ReferenceUp, fov);
  400. float min = (screenRect.yMin - 0.5f) * fov;
  401. float max = (screenRect.yMax - 0.5f) * fov;
  402. if (rotToRect.x < min)
  403. rotToRect.x -= min;
  404. else if (rotToRect.x > max)
  405. rotToRect.x -= max;
  406. else
  407. rotToRect.x = 0;
  408. min = (screenRect.xMin - 0.5f) * fovH;
  409. max = (screenRect.xMax - 0.5f) * fovH;
  410. if (rotToRect.y < min)
  411. rotToRect.y -= min;
  412. else if (rotToRect.y > max)
  413. rotToRect.y -= max;
  414. else
  415. rotToRect.y = 0;
  416. // Apply damping
  417. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  418. {
  419. rotToRect.x = VirtualCamera.DetachedLookAtTargetDamp(
  420. rotToRect.x, m_VerticalDamping, deltaTime);
  421. rotToRect.y = VirtualCamera.DetachedLookAtTargetDamp(
  422. rotToRect.y, m_HorizontalDamping, deltaTime);
  423. }
  424. // Rotate
  425. rigOrientation = rigOrientation.ApplyCameraRotation(rotToRect, state.ReferenceUp);
  426. }
  427. /// <summary>
  428. /// Prevent upside-down camera situation. This can happen if we have a high
  429. /// camera pitch combined with composer settings that cause the camera to tilt
  430. /// beyond the vertical in order to produce the desired framing. We prevent this by
  431. /// clamping the composer's vertical settings so that this situation can't happen.
  432. /// </summary>
  433. private bool ClampVerticalBounds(ref Rect r, Vector3 dir, Vector3 up, float fov)
  434. {
  435. float angle = UnityVectorExtensions.Angle(dir, up);
  436. float halfFov = (fov / 2f) + 1; // give it a little extra to accommodate precision errors
  437. if (angle < halfFov)
  438. {
  439. // looking up
  440. float maxY = 1f - (halfFov - angle) / fov;
  441. if (r.yMax > maxY)
  442. {
  443. r.yMin = Mathf.Min(r.yMin, maxY);
  444. r.yMax = Mathf.Min(r.yMax, maxY);
  445. return true;
  446. }
  447. }
  448. if (angle > (180 - halfFov))
  449. {
  450. // looking down
  451. float minY = (angle - (180 - halfFov)) / fov;
  452. if (minY > r.yMin)
  453. {
  454. r.yMin = Mathf.Max(r.yMin, minY);
  455. r.yMax = Mathf.Max(r.yMax, minY);
  456. return true;
  457. }
  458. }
  459. return false;
  460. }
  461. }
  462. }