CinemachineComposer.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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. m_Predictor.Smoothing = m_LookaheadSmoothing;
  137. m_Predictor.AddPosition(pos, VirtualCamera.PreviousStateIsValid ? deltaTime : -1, m_LookaheadTime);
  138. var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime);
  139. if (m_LookaheadIgnoreY)
  140. delta = delta.ProjectOntoPlane(up);
  141. TrackedPoint = pos + delta;
  142. }
  143. return pos;
  144. }
  145. /// <summary>State information for damping</summary>
  146. Vector3 m_CameraPosPrevFrame = Vector3.zero;
  147. Vector3 m_LookAtPrevFrame = Vector3.zero;
  148. Vector2 m_ScreenOffsetPrevFrame = Vector2.zero;
  149. Quaternion m_CameraOrientationPrevFrame = Quaternion.identity;
  150. internal PositionPredictor m_Predictor = new PositionPredictor();
  151. /// <summary>This is called to notify the us that a target got warped,
  152. /// so that we can update its internal state to make the camera
  153. /// also warp seamlessy.</summary>
  154. /// <param name="target">The object that was warped</param>
  155. /// <param name="positionDelta">The amount the target's position changed</param>
  156. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  157. {
  158. base.OnTargetObjectWarped(target, positionDelta);
  159. if (target == LookAtTarget)
  160. {
  161. m_CameraPosPrevFrame += positionDelta;
  162. m_LookAtPrevFrame += positionDelta;
  163. m_Predictor.ApplyTransformDelta(positionDelta);
  164. }
  165. }
  166. /// <summary>
  167. /// Force the virtual camera to assume a given position and orientation
  168. /// </summary>
  169. /// <param name="pos">Worldspace pposition to take</param>
  170. /// <param name="rot">Worldspace orientation to take</param>
  171. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  172. {
  173. base.ForceCameraPosition(pos, rot);
  174. m_CameraPosPrevFrame = pos;
  175. m_CameraOrientationPrevFrame = rot;
  176. }
  177. /// <summary>
  178. /// Report maximum damping time needed for this component.
  179. /// </summary>
  180. /// <returns>Highest damping setting in this component</returns>
  181. public override float GetMaxDampTime()
  182. {
  183. return Mathf.Max(m_HorizontalDamping, m_VerticalDamping);
  184. }
  185. /// <summary>Sets the state's ReferenceLookAt, applying the offset.</summary>
  186. /// <param name="curState">Input state that must be mutated</param>
  187. /// <param name="deltaTime">Current effective deltaTime</param>
  188. public override void PrePipelineMutateCameraState(ref CameraState curState, float deltaTime)
  189. {
  190. if (IsValid && curState.HasLookAt)
  191. curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(
  192. curState.ReferenceLookAt, curState.ReferenceUp, deltaTime);
  193. }
  194. /// <summary>Applies the composer rules and orients the camera accordingly</summary>
  195. /// <param name="curState">The current camera state</param>
  196. /// <param name="deltaTime">Used for calculating damping. If less than
  197. /// zero, then target will snap to the center of the dead zone.</param>
  198. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  199. {
  200. if (!IsValid || !curState.HasLookAt)
  201. return;
  202. // Correct the tracked point in the event that it's behind the camera
  203. // while the real target is in front
  204. if (!(TrackedPoint - curState.ReferenceLookAt).AlmostZero())
  205. {
  206. Vector3 mid = Vector3.Lerp(curState.CorrectedPosition, curState.ReferenceLookAt, 0.5f);
  207. Vector3 toLookAt = curState.ReferenceLookAt - mid;
  208. Vector3 toTracked = TrackedPoint - mid;
  209. if (Vector3.Dot(toLookAt, toTracked) < 0)
  210. {
  211. float t = Vector3.Distance(curState.ReferenceLookAt, mid)
  212. / Vector3.Distance(curState.ReferenceLookAt, TrackedPoint);
  213. TrackedPoint = Vector3.Lerp(curState.ReferenceLookAt, TrackedPoint, t);
  214. }
  215. }
  216. float targetDistance = (TrackedPoint - curState.CorrectedPosition).magnitude;
  217. if (targetDistance < Epsilon)
  218. {
  219. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  220. curState.RawOrientation = m_CameraOrientationPrevFrame;
  221. return; // navel-gazing, get outa here
  222. }
  223. // Expensive FOV calculations
  224. mCache.UpdateCache(curState.Lens, SoftGuideRect, HardGuideRect, targetDistance);
  225. Quaternion rigOrientation = curState.RawOrientation;
  226. if (deltaTime < 0 || !VirtualCamera.PreviousStateIsValid)
  227. {
  228. // No damping, just snap to central bounds, skipping the soft zone
  229. rigOrientation = Quaternion.LookRotation(
  230. rigOrientation * Vector3.forward, curState.ReferenceUp);
  231. Rect rect = mCache.mFovSoftGuideRect;
  232. if (m_CenterOnActivate)
  233. rect = new Rect(rect.center, Vector2.zero); // Force to center
  234. RotateToScreenBounds(
  235. ref curState, rect, curState.ReferenceLookAt,
  236. ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
  237. }
  238. else
  239. {
  240. // Start with previous frame's orientation (but with current up)
  241. Vector3 dir = m_LookAtPrevFrame - m_CameraPosPrevFrame;
  242. if (dir.AlmostZero())
  243. rigOrientation = Quaternion.LookRotation(
  244. m_CameraOrientationPrevFrame * Vector3.forward, curState.ReferenceUp);
  245. else
  246. {
  247. dir = Quaternion.Euler(curState.PositionDampingBypass) * dir;
  248. rigOrientation = Quaternion.LookRotation(dir, curState.ReferenceUp);
  249. rigOrientation = rigOrientation.ApplyCameraRotation(
  250. -m_ScreenOffsetPrevFrame, curState.ReferenceUp);
  251. }
  252. // Move target through the soft zone, with damping
  253. RotateToScreenBounds(
  254. ref curState, mCache.mFovSoftGuideRect, TrackedPoint,
  255. ref rigOrientation, mCache.mFov, mCache.mFovH, deltaTime);
  256. // Force the actual target (not the lookahead one) into the hard bounds, no damping
  257. if (deltaTime < 0 || VirtualCamera.LookAtTargetAttachment > 1 - Epsilon)
  258. RotateToScreenBounds(
  259. ref curState, mCache.mFovHardGuideRect, curState.ReferenceLookAt,
  260. ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
  261. }
  262. m_CameraPosPrevFrame = curState.CorrectedPosition;
  263. m_LookAtPrevFrame = TrackedPoint;
  264. m_CameraOrientationPrevFrame = UnityQuaternionExtensions.Normalized(rigOrientation);
  265. m_ScreenOffsetPrevFrame = m_CameraOrientationPrevFrame.GetCameraRotationToTarget(
  266. m_LookAtPrevFrame - curState.CorrectedPosition, curState.ReferenceUp);
  267. curState.RawOrientation = m_CameraOrientationPrevFrame;
  268. }
  269. /// <summary>Internal API for the inspector editor</summary>
  270. internal Rect SoftGuideRect
  271. {
  272. get
  273. {
  274. return new Rect(
  275. m_ScreenX - m_DeadZoneWidth / 2, m_ScreenY - m_DeadZoneHeight / 2,
  276. m_DeadZoneWidth, m_DeadZoneHeight);
  277. }
  278. set
  279. {
  280. m_DeadZoneWidth = Mathf.Clamp(value.width, 0, 2);
  281. m_DeadZoneHeight = Mathf.Clamp(value.height, 0, 2);
  282. m_ScreenX = Mathf.Clamp(value.x + m_DeadZoneWidth / 2, -0.5f, 1.5f);
  283. m_ScreenY = Mathf.Clamp(value.y + m_DeadZoneHeight / 2, -0.5f, 1.5f);
  284. m_SoftZoneWidth = Mathf.Max(m_SoftZoneWidth, m_DeadZoneWidth);
  285. m_SoftZoneHeight = Mathf.Max(m_SoftZoneHeight, m_DeadZoneHeight);
  286. }
  287. }
  288. /// <summary>Internal API for the inspector editor</summary>
  289. internal Rect HardGuideRect
  290. {
  291. get
  292. {
  293. Rect r = new Rect(
  294. m_ScreenX - m_SoftZoneWidth / 2, m_ScreenY - m_SoftZoneHeight / 2,
  295. m_SoftZoneWidth, m_SoftZoneHeight);
  296. r.position += new Vector2(
  297. m_BiasX * (m_SoftZoneWidth - m_DeadZoneWidth),
  298. m_BiasY * (m_SoftZoneHeight - m_DeadZoneHeight));
  299. return r;
  300. }
  301. set
  302. {
  303. m_SoftZoneWidth = Mathf.Clamp(value.width, 0, 2f);
  304. m_SoftZoneHeight = Mathf.Clamp(value.height, 0, 2f);
  305. m_DeadZoneWidth = Mathf.Min(m_DeadZoneWidth, m_SoftZoneWidth);
  306. m_DeadZoneHeight = Mathf.Min(m_DeadZoneHeight, m_SoftZoneHeight);
  307. Vector2 center = value.center;
  308. Vector2 bias = center - new Vector2(m_ScreenX, m_ScreenY);
  309. float biasWidth = Mathf.Max(0, m_SoftZoneWidth - m_DeadZoneWidth);
  310. float biasHeight = Mathf.Max(0, m_SoftZoneHeight - m_DeadZoneHeight);
  311. m_BiasX = biasWidth < Epsilon ? 0 : Mathf.Clamp(bias.x / biasWidth, -0.5f, 0.5f);
  312. m_BiasY = biasHeight < Epsilon ? 0 : Mathf.Clamp(bias.y / biasHeight, -0.5f, 0.5f);
  313. }
  314. }
  315. // Cache for some expensive calculations
  316. struct FovCache
  317. {
  318. public Rect mFovSoftGuideRect;
  319. public Rect mFovHardGuideRect;
  320. public float mFovH;
  321. public float mFov;
  322. float mOrthoSizeOverDistance;
  323. float mAspect;
  324. Rect mSoftGuideRect;
  325. Rect mHardGuideRect;
  326. public void UpdateCache(
  327. LensSettings lens, Rect softGuide, Rect hardGuide, float targetDistance)
  328. {
  329. bool recalculate = mAspect != lens.Aspect
  330. || softGuide != mSoftGuideRect || hardGuide != mHardGuideRect;
  331. if (lens.Orthographic)
  332. {
  333. float orthoOverDistance = Mathf.Abs(lens.OrthographicSize / targetDistance);
  334. if (mOrthoSizeOverDistance == 0
  335. || Mathf.Abs(orthoOverDistance - mOrthoSizeOverDistance) / mOrthoSizeOverDistance
  336. > mOrthoSizeOverDistance * 0.01f)
  337. recalculate = true;
  338. if (recalculate)
  339. {
  340. // Calculate effective fov - fake it for ortho based on target distance
  341. mFov = Mathf.Rad2Deg * 2 * Mathf.Atan(orthoOverDistance);
  342. mFovH = Mathf.Rad2Deg * 2 * Mathf.Atan(lens.Aspect * orthoOverDistance);
  343. mOrthoSizeOverDistance = orthoOverDistance;
  344. }
  345. }
  346. else
  347. {
  348. if (mFov != lens.FieldOfView)
  349. recalculate = true;
  350. if (recalculate)
  351. {
  352. mFov = lens.FieldOfView;
  353. double radHFOV = 2 * Math.Atan(Math.Tan(mFov * Mathf.Deg2Rad / 2) * lens.Aspect);
  354. mFovH = (float)(Mathf.Rad2Deg * radHFOV);
  355. mOrthoSizeOverDistance = 0;
  356. }
  357. }
  358. if (recalculate)
  359. {
  360. mFovSoftGuideRect = ScreenToFOV(softGuide, mFov, mFovH, lens.Aspect);
  361. mSoftGuideRect = softGuide;
  362. mFovHardGuideRect = ScreenToFOV(hardGuide, mFov, mFovH, lens.Aspect);
  363. mHardGuideRect = hardGuide;
  364. mAspect = lens.Aspect;
  365. }
  366. }
  367. // Convert from screen coords to normalized FOV angular coords
  368. private Rect ScreenToFOV(Rect rScreen, float fov, float fovH, float aspect)
  369. {
  370. Rect r = new Rect(rScreen);
  371. Matrix4x4 persp = Matrix4x4.Perspective(fov, aspect, 0.0001f, 2f).inverse;
  372. Vector3 p = persp.MultiplyPoint(new Vector3(0, (r.yMin * 2f) - 1f, 0.5f)); p.z = -p.z;
  373. float angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
  374. r.yMin = ((fov / 2) + angle) / fov;
  375. p = persp.MultiplyPoint(new Vector3(0, (r.yMax * 2f) - 1f, 0.5f)); p.z = -p.z;
  376. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
  377. r.yMax = ((fov / 2) + angle) / fov;
  378. p = persp.MultiplyPoint(new Vector3((r.xMin * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
  379. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
  380. r.xMin = ((fovH / 2) + angle) / fovH;
  381. p = persp.MultiplyPoint(new Vector3((r.xMax * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
  382. angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
  383. r.xMax = ((fovH / 2) + angle) / fovH;
  384. return r;
  385. }
  386. }
  387. FovCache mCache;
  388. /// <summary>
  389. /// Adjust the rigOrientation to put the camera within the screen bounds.
  390. /// If deltaTime >= 0 then damping will be applied.
  391. /// Assumes that currentOrientation fwd is such that input rigOrientation's
  392. /// local up is NEVER NEVER NEVER pointing downwards, relative to
  393. /// state.ReferenceUp. If this condition is violated
  394. /// then you will see crazy spinning. That's the symptom.
  395. /// </summary>
  396. private void RotateToScreenBounds(
  397. ref CameraState state, Rect screenRect, Vector3 trackedPoint,
  398. ref Quaternion rigOrientation, float fov, float fovH, float deltaTime)
  399. {
  400. Vector3 targetDir = trackedPoint - state.CorrectedPosition;
  401. Vector2 rotToRect = rigOrientation.GetCameraRotationToTarget(targetDir, state.ReferenceUp);
  402. // Bring it to the edge of screenRect, if outside. Leave it alone if inside.
  403. ClampVerticalBounds(ref screenRect, targetDir, state.ReferenceUp, fov);
  404. float min = (screenRect.yMin - 0.5f) * fov;
  405. float max = (screenRect.yMax - 0.5f) * fov;
  406. if (rotToRect.x < min)
  407. rotToRect.x -= min;
  408. else if (rotToRect.x > max)
  409. rotToRect.x -= max;
  410. else
  411. rotToRect.x = 0;
  412. min = (screenRect.xMin - 0.5f) * fovH;
  413. max = (screenRect.xMax - 0.5f) * fovH;
  414. if (rotToRect.y < min)
  415. rotToRect.y -= min;
  416. else if (rotToRect.y > max)
  417. rotToRect.y -= max;
  418. else
  419. rotToRect.y = 0;
  420. // Apply damping
  421. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  422. {
  423. rotToRect.x = VirtualCamera.DetachedLookAtTargetDamp(
  424. rotToRect.x, m_VerticalDamping, deltaTime);
  425. rotToRect.y = VirtualCamera.DetachedLookAtTargetDamp(
  426. rotToRect.y, m_HorizontalDamping, deltaTime);
  427. }
  428. // Rotate
  429. rigOrientation = rigOrientation.ApplyCameraRotation(rotToRect, state.ReferenceUp);
  430. }
  431. /// <summary>
  432. /// Prevent upside-down camera situation. This can happen if we have a high
  433. /// camera pitch combined with composer settings that cause the camera to tilt
  434. /// beyond the vertical in order to produce the desired framing. We prevent this by
  435. /// clamping the composer's vertical settings so that this situation can't happen.
  436. /// </summary>
  437. private bool ClampVerticalBounds(ref Rect r, Vector3 dir, Vector3 up, float fov)
  438. {
  439. float angle = UnityVectorExtensions.Angle(dir, up);
  440. float halfFov = (fov / 2f) + 1; // give it a little extra to accommodate precision errors
  441. if (angle < halfFov)
  442. {
  443. // looking up
  444. float maxY = 1f - (halfFov - angle) / fov;
  445. if (r.yMax > maxY)
  446. {
  447. r.yMin = Mathf.Min(r.yMin, maxY);
  448. r.yMax = Mathf.Min(r.yMax, maxY);
  449. return true;
  450. }
  451. }
  452. if (angle > (180 - halfFov))
  453. {
  454. // looking down
  455. float minY = (angle - (180 - halfFov)) / fov;
  456. if (minY > r.yMin)
  457. {
  458. r.yMin = Mathf.Max(r.yMin, minY);
  459. r.yMax = Mathf.Max(r.yMax, minY);
  460. return true;
  461. }
  462. }
  463. return false;
  464. }
  465. }
  466. }