CinemachineFramingTransposer.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. using System;
  2. using Cinemachine.Utility;
  3. using UnityEngine;
  4. using UnityEngine.Serialization;
  5. namespace Cinemachine
  6. {
  7. /// <summary>
  8. /// This is a Cinemachine Component in the Body section of the component pipeline.
  9. /// Its job is to position the camera in a fixed screen-space relationship to
  10. /// the vcam's Follow target object, with offsets and damping.
  11. ///
  12. /// The camera will be first moved along the camera Z axis until the Follow target
  13. /// is at the desired distance from the camera's X-Y plane. The camera will then
  14. /// be moved in its XY plane until the Follow target is at the desired point on
  15. /// the camera's screen.
  16. ///
  17. /// The FramingTansposer will only change the camera's position in space. It will not
  18. /// re-orient or otherwise aim the camera.
  19. ///
  20. /// For this component to work properly, the vcam's LookAt target must be null.
  21. /// The Follow target will define what the camera is looking at.
  22. ///
  23. /// If the Follow target is a ICinemachineTargetGroup, then additional controls will
  24. /// be available to dynamically adjust the camera's view in order to frame the entire group.
  25. ///
  26. /// Although this component was designed for orthographic cameras, it works equally
  27. /// well with persective cameras and can be used in 3D environments.
  28. /// </summary>
  29. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  30. [AddComponentMenu("")] // Don't display in add component menu
  31. [SaveDuringPlay]
  32. public class CinemachineFramingTransposer : CinemachineComponentBase
  33. {
  34. /// <summary>
  35. /// Offset from the Follow Target object (in target-local co-ordinates). The camera will attempt to
  36. /// frame the point which is the target's position plus this offset. Use it to correct for
  37. /// cases when the target's origin is not the point of interest for the camera.
  38. /// </summary>
  39. [Tooltip("Offset from the Follow Target object (in target-local co-ordinates). "
  40. + "The camera will attempt to frame the point which is the target's position plus "
  41. + "this offset. Use it to correct for cases when the target's origin is not the "
  42. + "point of interest for the camera.")]
  43. public Vector3 m_TrackedObjectOffset;
  44. /// <summary>This setting will instruct the composer to adjust its target offset based
  45. /// on the motion of the target. The composer will look at a point where it estimates
  46. /// the target will be this many seconds into the future. Note that this setting is sensitive
  47. /// to noisy animation, and can amplify the noise, resulting in undesirable camera jitter.
  48. /// If the camera jitters unacceptably when the target is in motion, turn down this setting,
  49. /// or animate the target more smoothly.</summary>
  50. [Tooltip("This setting will instruct the composer to adjust its target offset based on the "
  51. + "motion of the target. The composer will look at a point where it estimates the target "
  52. + "will be this many seconds into the future. Note that this setting is sensitive to noisy "
  53. + "animation, and can amplify the noise, resulting in undesirable camera jitter. "
  54. + "If the camera jitters unacceptably when the target is in motion, turn down this "
  55. + "setting, or animate the target more smoothly.")]
  56. [Range(0f, 1f)]
  57. [Space]
  58. public float m_LookaheadTime = 0;
  59. /// <summary>Controls the smoothness of the lookahead algorithm. Larger values smooth out
  60. /// jittery predictions and also increase prediction lag</summary>
  61. [Tooltip("Controls the smoothness of the lookahead algorithm. Larger values smooth out "
  62. + "jittery predictions and also increase prediction lag")]
  63. [Range(0, 30)]
  64. public float m_LookaheadSmoothing = 0;
  65. /// <summary>If checked, movement along the Y axis will be ignored for lookahead calculations</summary>
  66. [Tooltip("If checked, movement along the Y axis will be ignored for lookahead calculations")]
  67. public bool m_LookaheadIgnoreY;
  68. /// <summary>How aggressively the camera tries to maintain the offset in the X-axis.
  69. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  70. /// x-axis offset. Larger numbers give a more heavy slowly responding camera.
  71. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  72. [Space]
  73. [Range(0f, 20f)]
  74. [Tooltip("How aggressively the camera tries to maintain the offset in the X-axis. "
  75. + "Small numbers are more responsive, rapidly translating the camera to keep the target's "
  76. + "x-axis offset. Larger numbers give a more heavy slowly responding camera. "
  77. + "Using different settings per axis can yield a wide range of camera behaviors.")]
  78. public float m_XDamping = 1f;
  79. /// <summary>How aggressively the camera tries to maintain the offset in the Y-axis.
  80. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  81. /// y-axis offset. Larger numbers give a more heavy slowly responding camera.
  82. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  83. [Range(0f, 20f)]
  84. [Tooltip("How aggressively the camera tries to maintain the offset in the Y-axis. "
  85. + "Small numbers are more responsive, rapidly translating the camera to keep the target's "
  86. + "y-axis offset. Larger numbers give a more heavy slowly responding camera. "
  87. + "Using different settings per axis can yield a wide range of camera behaviors.")]
  88. public float m_YDamping = 1f;
  89. /// <summary>How aggressively the camera tries to maintain the offset in the Z-axis.
  90. /// Small numbers are more responsive, rapidly translating the camera to keep the
  91. /// target's z-axis offset. Larger numbers give a more heavy slowly responding camera.
  92. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  93. [Range(0f, 20f)]
  94. [Tooltip("How aggressively the camera tries to maintain the offset in the Z-axis. "
  95. + "Small numbers are more responsive, rapidly translating the camera to keep the target's "
  96. + "z-axis offset. Larger numbers give a more heavy slowly responding camera. "
  97. + "Using different settings per axis can yield a wide range of camera behaviors.")]
  98. public float m_ZDamping = 1f;
  99. /// <summary>If set, damping will apply only to target motion, and not when
  100. /// the camera rotation changes. Turn this on to get an instant response when
  101. /// the rotation changes</summary>
  102. [Tooltip("If set, damping will apply only to target motion, but not to camera "
  103. + "rotation changes. Turn this on to get an instant response when the rotation changes. ")]
  104. public bool m_TargetMovementOnly = true;
  105. /// <summary>Horizontal screen position for target. The camera will move to position the tracked object here</summary>
  106. [Space]
  107. [Range(-0.5f, 1.5f)]
  108. [Tooltip("Horizontal screen position for target. The camera will move to position the tracked object here.")]
  109. public float m_ScreenX = 0.5f;
  110. /// <summary>Vertical screen position for target, The camera will move to to position the tracked object here</summary>
  111. [Range(-0.5f, 1.5f)]
  112. [Tooltip("Vertical screen position for target, The camera will move to position the tracked object here.")]
  113. public float m_ScreenY = 0.5f;
  114. /// <summary>The distance along the camera axis that will be maintained from the Follow target</summary>
  115. [Tooltip("The distance along the camera axis that will be maintained from the Follow target")]
  116. public float m_CameraDistance = 10f;
  117. /// <summary>Camera will not move horizontally if the target is within this range of the position</summary>
  118. [Space]
  119. [Range(0f, 2f)]
  120. [Tooltip("Camera will not move horizontally if the target is within this range of the position.")]
  121. public float m_DeadZoneWidth = 0f;
  122. /// <summary>Camera will not move vertically if the target is within this range of the position</summary>
  123. [Range(0f, 2f)]
  124. [Tooltip("Camera will not move vertically if the target is within this range of the position.")]
  125. public float m_DeadZoneHeight = 0f;
  126. /// <summary>The camera will not move along its z-axis if the Follow target is within
  127. /// this distance of the specified camera distance</summary>
  128. [Tooltip("The camera will not move along its z-axis if the Follow target is within "
  129. + "this distance of the specified camera distance")]
  130. [FormerlySerializedAs("m_DistanceDeadZoneSize")]
  131. public float m_DeadZoneDepth = 0;
  132. /// <summary>If checked, then then soft zone will be unlimited in size</summary>
  133. [Space]
  134. [Tooltip("If checked, then then soft zone will be unlimited in size.")]
  135. public bool m_UnlimitedSoftZone = false;
  136. /// <summary>When target is within this region, camera will gradually move to re-align
  137. /// towards the desired position, depending onm the damping speed</summary>
  138. [Range(0f, 2f)]
  139. [Tooltip("When target is within this region, camera will gradually move horizontally to "
  140. + "re-align towards the desired position, depending on the damping speed.")]
  141. public float m_SoftZoneWidth = 0.8f;
  142. /// <summary>When target is within this region, camera will gradually move to re-align
  143. /// towards the desired position, depending onm the damping speed</summary>
  144. [Range(0f, 2f)]
  145. [Tooltip("When target is within this region, camera will gradually move vertically to "
  146. + "re-align towards the desired position, depending on the damping speed.")]
  147. public float m_SoftZoneHeight = 0.8f;
  148. /// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
  149. [Range(-0.5f, 0.5f)]
  150. [Tooltip("A non-zero bias will move the target position horizontally away from the center of the soft zone.")]
  151. public float m_BiasX = 0f;
  152. /// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
  153. [Range(-0.5f, 0.5f)]
  154. [Tooltip("A non-zero bias will move the target position vertically away from the center of the soft zone.")]
  155. public float m_BiasY = 0f;
  156. /// <summary>Force target to center of screen when this camera activates.
  157. /// If false, will clamp target to the edges of the dead zone</summary>
  158. [Tooltip("Force target to center of screen when this camera activates. "
  159. + "If false, will clamp target to the edges of the dead zone")]
  160. public bool m_CenterOnActivate = true;
  161. /// <summary>What screen dimensions to consider when framing</summary>
  162. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  163. public enum FramingMode
  164. {
  165. /// <summary>Consider only the horizontal dimension. Vertical framing is ignored.</summary>
  166. Horizontal,
  167. /// <summary>Consider only the vertical dimension. Horizontal framing is ignored.</summary>
  168. Vertical,
  169. /// <summary>The larger of the horizontal and vertical dimensions will dominate, to get the best fit.</summary>
  170. HorizontalAndVertical,
  171. /// <summary>Don't do any framing adjustment</summary>
  172. None
  173. };
  174. /// <summary>What screen dimensions to consider when framing</summary>
  175. [Space]
  176. [Tooltip("What screen dimensions to consider when framing. Can be Horizontal, Vertical, or both")]
  177. [FormerlySerializedAs("m_FramingMode")]
  178. public FramingMode m_GroupFramingMode = FramingMode.HorizontalAndVertical;
  179. /// <summary>How to adjust the camera to get the desired framing</summary>
  180. public enum AdjustmentMode
  181. {
  182. /// <summary>Do not move the camera, only adjust the FOV.</summary>
  183. ZoomOnly,
  184. /// <summary>Just move the camera, don't change the FOV.</summary>
  185. DollyOnly,
  186. /// <summary>Move the camera as much as permitted by the ranges, then
  187. /// adjust the FOV if necessary to make the shot.</summary>
  188. DollyThenZoom
  189. };
  190. /// <summary>How to adjust the camera to get the desired framing</summary>
  191. [Tooltip("How to adjust the camera to get the desired framing. You can zoom, dolly in/out, or do both.")]
  192. public AdjustmentMode m_AdjustmentMode = AdjustmentMode.ZoomOnly;
  193. /// <summary>How much of the screen to fill with the bounding box of the targets.</summary>
  194. [Tooltip("The bounding box of the targets should occupy this amount of the screen space. "
  195. + "1 means fill the whole screen. 0.5 means fill half the screen, etc.")]
  196. public float m_GroupFramingSize = 0.8f;
  197. /// <summary>How much closer to the target can the camera go?</summary>
  198. [Tooltip("The maximum distance toward the target that this behaviour is allowed to move the camera.")]
  199. public float m_MaxDollyIn = 5000f;
  200. /// <summary>How much farther from the target can the camera go?</summary>
  201. [Tooltip("The maximum distance away the target that this behaviour is allowed to move the camera.")]
  202. public float m_MaxDollyOut = 5000f;
  203. /// <summary>Set this to limit how close to the target the camera can get</summary>
  204. [Tooltip("Set this to limit how close to the target the camera can get.")]
  205. public float m_MinimumDistance = 1;
  206. /// <summary>Set this to limit how far from the taregt the camera can get</summary>
  207. [Tooltip("Set this to limit how far from the target the camera can get.")]
  208. public float m_MaximumDistance = 5000f;
  209. /// <summary>If adjusting FOV, will not set the FOV lower than this</summary>
  210. [Range(1, 179)]
  211. [Tooltip("If adjusting FOV, will not set the FOV lower than this.")]
  212. public float m_MinimumFOV = 3;
  213. /// <summary>If adjusting FOV, will not set the FOV higher than this</summary>
  214. [Range(1, 179)]
  215. [Tooltip("If adjusting FOV, will not set the FOV higher than this.")]
  216. public float m_MaximumFOV = 60;
  217. /// <summary>If adjusting Orthographic Size, will not set it lower than this</summary>
  218. [Tooltip("If adjusting Orthographic Size, will not set it lower than this.")]
  219. public float m_MinimumOrthoSize = 1;
  220. /// <summary>If adjusting Orthographic Size, will not set it higher than this</summary>
  221. [Tooltip("If adjusting Orthographic Size, will not set it higher than this.")]
  222. public float m_MaximumOrthoSize = 5000;
  223. /// <summary>Internal API for the inspector editor</summary>
  224. internal Rect SoftGuideRect
  225. {
  226. get
  227. {
  228. return new Rect(
  229. m_ScreenX - m_DeadZoneWidth / 2, m_ScreenY - m_DeadZoneHeight / 2,
  230. m_DeadZoneWidth, m_DeadZoneHeight);
  231. }
  232. set
  233. {
  234. m_DeadZoneWidth = Mathf.Clamp(value.width, 0, 2);
  235. m_DeadZoneHeight = Mathf.Clamp(value.height, 0, 2);
  236. m_ScreenX = Mathf.Clamp(value.x + m_DeadZoneWidth / 2, -0.5f, 1.5f);
  237. m_ScreenY = Mathf.Clamp(value.y + m_DeadZoneHeight / 2, -0.5f, 1.5f);
  238. m_SoftZoneWidth = Mathf.Max(m_SoftZoneWidth, m_DeadZoneWidth);
  239. m_SoftZoneHeight = Mathf.Max(m_SoftZoneHeight, m_DeadZoneHeight);
  240. }
  241. }
  242. /// <summary>Internal API for the inspector editor</summary>
  243. internal Rect HardGuideRect
  244. {
  245. get
  246. {
  247. Rect r = new Rect(
  248. m_ScreenX - m_SoftZoneWidth / 2, m_ScreenY - m_SoftZoneHeight / 2,
  249. m_SoftZoneWidth, m_SoftZoneHeight);
  250. r.position += new Vector2(
  251. m_BiasX * (m_SoftZoneWidth - m_DeadZoneWidth),
  252. m_BiasY * (m_SoftZoneHeight - m_DeadZoneHeight));
  253. return r;
  254. }
  255. set
  256. {
  257. m_SoftZoneWidth = Mathf.Clamp(value.width, 0, 2f);
  258. m_SoftZoneHeight = Mathf.Clamp(value.height, 0, 2f);
  259. m_DeadZoneWidth = Mathf.Min(m_DeadZoneWidth, m_SoftZoneWidth);
  260. m_DeadZoneHeight = Mathf.Min(m_DeadZoneHeight, m_SoftZoneHeight);
  261. }
  262. }
  263. void OnValidate()
  264. {
  265. m_CameraDistance = Mathf.Max(m_CameraDistance, kMinimumCameraDistance);
  266. m_DeadZoneDepth = Mathf.Max(m_DeadZoneDepth, 0);
  267. m_GroupFramingSize = Mathf.Max(0.001f, m_GroupFramingSize);
  268. m_MaxDollyIn = Mathf.Max(0, m_MaxDollyIn);
  269. m_MaxDollyOut = Mathf.Max(0, m_MaxDollyOut);
  270. m_MinimumDistance = Mathf.Max(0, m_MinimumDistance);
  271. m_MaximumDistance = Mathf.Max(m_MinimumDistance, m_MaximumDistance);
  272. m_MinimumFOV = Mathf.Max(1, m_MinimumFOV);
  273. m_MaximumFOV = Mathf.Clamp(m_MaximumFOV, m_MinimumFOV, 179);
  274. m_MinimumOrthoSize = Mathf.Max(0.01f, m_MinimumOrthoSize);
  275. m_MaximumOrthoSize = Mathf.Max(m_MinimumOrthoSize, m_MaximumOrthoSize);
  276. }
  277. /// <summary>True if component is enabled and has a valid Follow target</summary>
  278. public override bool IsValid { get { return enabled && FollowTarget != null; } }
  279. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  280. /// Always returns the Body stage</summary>
  281. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  282. /// <summary>FramingTransposer's algorithm tahes camera orientation as input,
  283. /// so even though it is a Body component, it must apply after Aim</summary>
  284. public override bool BodyAppliesAfterAim { get { return true; } }
  285. const float kMinimumCameraDistance = 0.01f;
  286. const float kMinimumGroupSize = 0.01f;
  287. /// <summary>State information for damping</summary>
  288. Vector3 m_PreviousCameraPosition = Vector3.zero;
  289. internal PositionPredictor m_Predictor = new PositionPredictor();
  290. /// <summary>Internal API for inspector</summary>
  291. public Vector3 TrackedPoint { get; private set; }
  292. /// <summary>This is called to notify the us that a target got warped,
  293. /// so that we can update its internal state to make the camera
  294. /// also warp seamlessy.</summary>
  295. /// <param name="target">The object that was warped</param>
  296. /// <param name="positionDelta">The amount the target's position changed</param>
  297. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  298. {
  299. base.OnTargetObjectWarped(target, positionDelta);
  300. if (target == FollowTarget)
  301. {
  302. m_PreviousCameraPosition += positionDelta;
  303. m_Predictor.ApplyTransformDelta(positionDelta);
  304. }
  305. }
  306. /// <summary>
  307. /// Force the virtual camera to assume a given position and orientation
  308. /// </summary>
  309. /// <param name="pos">Worldspace pposition to take</param>
  310. /// <param name="rot">Worldspace orientation to take</param>
  311. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  312. {
  313. base.ForceCameraPosition(pos, rot);
  314. m_PreviousCameraPosition = pos;
  315. m_prevRotation = rot;
  316. }
  317. /// <summary>
  318. /// Report maximum damping time needed for this component.
  319. /// </summary>
  320. /// <returns>Highest damping setting in this component</returns>
  321. public override float GetMaxDampTime()
  322. {
  323. return Mathf.Max(m_XDamping, Mathf.Max(m_YDamping, m_ZDamping));
  324. }
  325. /// <summary>Notification that this virtual camera is going live.
  326. /// Base class implementation does nothing.</summary>
  327. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  328. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  329. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  330. /// <param name="transitionParams">Transition settings for this vcam</param>
  331. /// <returns>True if the vcam should do an internal update as a result of this call</returns>
  332. public override bool OnTransitionFromCamera(
  333. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime,
  334. ref CinemachineVirtualCameraBase.TransitionParams transitionParams)
  335. {
  336. if (fromCam != null && transitionParams.m_InheritPosition
  337. && !CinemachineCore.Instance.IsLiveInBlend(VirtualCamera))
  338. {
  339. m_PreviousCameraPosition = fromCam.State.RawPosition;
  340. m_prevRotation = fromCam.State.RawOrientation;
  341. m_InheritingPosition = true;
  342. return true;
  343. }
  344. return false;
  345. }
  346. bool m_InheritingPosition;
  347. // Convert from screen coords to normalized orthographic distance coords
  348. private Rect ScreenToOrtho(Rect rScreen, float orthoSize, float aspect)
  349. {
  350. Rect r = new Rect();
  351. r.yMax = 2 * orthoSize * ((1f-rScreen.yMin) - 0.5f);
  352. r.yMin = 2 * orthoSize * ((1f-rScreen.yMax) - 0.5f);
  353. r.xMin = 2 * orthoSize * aspect * (rScreen.xMin - 0.5f);
  354. r.xMax = 2 * orthoSize * aspect * (rScreen.xMax - 0.5f);
  355. return r;
  356. }
  357. private Vector3 OrthoOffsetToScreenBounds(Vector3 targetPos2D, Rect screenRect)
  358. {
  359. // Bring it to the edge of screenRect, if outside. Leave it alone if inside.
  360. Vector3 delta = Vector3.zero;
  361. if (targetPos2D.x < screenRect.xMin)
  362. delta.x += targetPos2D.x - screenRect.xMin;
  363. if (targetPos2D.x > screenRect.xMax)
  364. delta.x += targetPos2D.x - screenRect.xMax;
  365. if (targetPos2D.y < screenRect.yMin)
  366. delta.y += targetPos2D.y - screenRect.yMin;
  367. if (targetPos2D.y > screenRect.yMax)
  368. delta.y += targetPos2D.y - screenRect.yMax;
  369. return delta;
  370. }
  371. float m_prevFOV; // State for frame damping
  372. Quaternion m_prevRotation;
  373. /// <summary>For editor visulaization of the calculated bounding box of the group</summary>
  374. public Bounds LastBounds { get; private set; }
  375. /// <summary>For editor visualization of the calculated bounding box of the group</summary>
  376. public Matrix4x4 LastBoundsMatrix { get; private set; }
  377. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  378. /// <param name="curState">The current camera state</param>
  379. /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
  380. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  381. {
  382. LensSettings lens = curState.Lens;
  383. Vector3 followTargetPosition = FollowTargetPosition + (FollowTargetRotation * m_TrackedObjectOffset);
  384. bool previousStateIsValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
  385. if (!previousStateIsValid || VirtualCamera.FollowTargetChanged)
  386. m_Predictor.Reset();
  387. if (!previousStateIsValid)
  388. {
  389. m_PreviousCameraPosition = curState.RawPosition;
  390. m_prevFOV = lens.Orthographic ? lens.OrthographicSize : lens.FieldOfView;
  391. m_prevRotation = curState.RawOrientation;
  392. if (!m_InheritingPosition && m_CenterOnActivate)
  393. {
  394. m_PreviousCameraPosition = FollowTargetPosition
  395. + (curState.RawOrientation * Vector3.back) * m_CameraDistance;
  396. }
  397. }
  398. if (!IsValid)
  399. {
  400. m_InheritingPosition = false;
  401. return;
  402. }
  403. var verticalFOV = lens.FieldOfView;
  404. // Compute group bounds and adjust follow target for group framing
  405. ICinemachineTargetGroup group = AbstractFollowTargetGroup;
  406. bool isGroupFraming = group != null && m_GroupFramingMode != FramingMode.None && !group.IsEmpty;
  407. if (isGroupFraming)
  408. followTargetPosition = ComputeGroupBounds(group, ref curState);
  409. TrackedPoint = followTargetPosition;
  410. if (m_LookaheadTime > Epsilon)
  411. {
  412. m_Predictor.Smoothing = m_LookaheadSmoothing;
  413. m_Predictor.AddPosition(followTargetPosition, deltaTime, m_LookaheadTime);
  414. var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime);
  415. if (m_LookaheadIgnoreY)
  416. delta = delta.ProjectOntoPlane(curState.ReferenceUp);
  417. var p = followTargetPosition + delta;
  418. if (isGroupFraming)
  419. {
  420. var b = LastBounds;
  421. b.center += LastBoundsMatrix.MultiplyPoint3x4(delta);
  422. LastBounds = b;
  423. }
  424. TrackedPoint = p;
  425. }
  426. if (!curState.HasLookAt)
  427. curState.ReferenceLookAt = followTargetPosition;
  428. // Adjust the desired depth for group framing
  429. float targetDistance = m_CameraDistance;
  430. bool isOrthographic = lens.Orthographic;
  431. float targetHeight = isGroupFraming ? GetTargetHeight(LastBounds.size / m_GroupFramingSize) : 0;
  432. targetHeight = Mathf.Max(targetHeight, kMinimumGroupSize);
  433. if (!isOrthographic && isGroupFraming)
  434. {
  435. // Adjust height for perspective - we want the height at the near surface
  436. float boundsDepth = LastBounds.extents.z;
  437. float z = LastBounds.center.z;
  438. if (z > boundsDepth)
  439. targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z);
  440. if (m_AdjustmentMode != AdjustmentMode.ZoomOnly)
  441. {
  442. // What distance from near edge would be needed to get the adjusted
  443. // target height, at the current FOV
  444. targetDistance = targetHeight / (2f * Mathf.Tan(verticalFOV * Mathf.Deg2Rad / 2f));
  445. // Clamp to respect min/max distance settings to the near surface of the bounds
  446. targetDistance = Mathf.Clamp(targetDistance, m_MinimumDistance, m_MaximumDistance);
  447. // Clamp to respect min/max camera movement
  448. float targetDelta = targetDistance - m_CameraDistance;
  449. targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut);
  450. targetDistance = m_CameraDistance + targetDelta;
  451. }
  452. }
  453. // Optionally allow undamped camera orientation change
  454. Quaternion localToWorld = curState.RawOrientation;
  455. if (previousStateIsValid && m_TargetMovementOnly)
  456. {
  457. var q = localToWorld * Quaternion.Inverse(m_prevRotation);
  458. m_PreviousCameraPosition = TrackedPoint + q * (m_PreviousCameraPosition - TrackedPoint);
  459. }
  460. m_prevRotation = localToWorld;
  461. // Adjust lens for group framing
  462. if (isGroupFraming)
  463. {
  464. if (isOrthographic)
  465. {
  466. targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize);
  467. // Apply Damping
  468. if (previousStateIsValid)
  469. targetHeight = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp(
  470. targetHeight - m_prevFOV, m_ZDamping, deltaTime);
  471. m_prevFOV = targetHeight;
  472. lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize);
  473. curState.Lens = lens;
  474. }
  475. else if (m_AdjustmentMode != AdjustmentMode.DollyOnly)
  476. {
  477. var localTarget = Quaternion.Inverse(curState.RawOrientation)
  478. * (followTargetPosition - curState.RawPosition);
  479. float nearBoundsDistance = localTarget.z;
  480. float targetFOV = 179;
  481. if (nearBoundsDistance > Epsilon)
  482. targetFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg;
  483. targetFOV = Mathf.Clamp(targetFOV, m_MinimumFOV, m_MaximumFOV);
  484. // ApplyDamping
  485. if (previousStateIsValid)
  486. targetFOV = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp(
  487. targetFOV - m_prevFOV, m_ZDamping, deltaTime);
  488. m_prevFOV = targetFOV;
  489. lens.FieldOfView = targetFOV;
  490. curState.Lens = lens;
  491. }
  492. }
  493. // Work in camera-local space
  494. Vector3 camPosWorld = m_PreviousCameraPosition;
  495. Quaternion worldToLocal = Quaternion.Inverse(localToWorld);
  496. Vector3 cameraPos = worldToLocal * camPosWorld;
  497. Vector3 targetPos = (worldToLocal * TrackedPoint) - cameraPos;
  498. Vector3 lookAtPos = targetPos;
  499. // Move along camera z
  500. Vector3 cameraOffset = Vector3.zero;
  501. float cameraMin = Mathf.Max(kMinimumCameraDistance, targetDistance - m_DeadZoneDepth/2);
  502. float cameraMax = Mathf.Max(cameraMin, targetDistance + m_DeadZoneDepth/2);
  503. float targetZ = Mathf.Min(targetPos.z, lookAtPos.z);
  504. if (targetZ < cameraMin)
  505. cameraOffset.z = targetZ - cameraMin;
  506. if (targetZ > cameraMax)
  507. cameraOffset.z = targetZ - cameraMax;
  508. // Move along the XY plane
  509. float screenSize = lens.Orthographic
  510. ? lens.OrthographicSize
  511. : Mathf.Tan(0.5f * verticalFOV * Mathf.Deg2Rad) * (targetZ - cameraOffset.z);
  512. Rect softGuideOrtho = ScreenToOrtho(SoftGuideRect, screenSize, lens.Aspect);
  513. if (!previousStateIsValid)
  514. {
  515. // No damping or hard bounds, just snap to central bounds, skipping the soft zone
  516. Rect rect = softGuideOrtho;
  517. if (m_CenterOnActivate && !m_InheritingPosition)
  518. rect = new Rect(rect.center, Vector2.zero); // Force to center
  519. cameraOffset += OrthoOffsetToScreenBounds(targetPos, rect);
  520. }
  521. else
  522. {
  523. // Move it through the soft zone, with damping
  524. cameraOffset += OrthoOffsetToScreenBounds(targetPos, softGuideOrtho);
  525. cameraOffset = VirtualCamera.DetachedFollowTargetDamp(
  526. cameraOffset, new Vector3(m_XDamping, m_YDamping, m_ZDamping), deltaTime);
  527. // Make sure the real target (not the lookahead one) is still in the frame
  528. if (!m_UnlimitedSoftZone
  529. && (deltaTime < 0 || VirtualCamera.FollowTargetAttachment > 1 - Epsilon))
  530. {
  531. Rect hardGuideOrtho = ScreenToOrtho(HardGuideRect, screenSize, lens.Aspect);
  532. var realTargetPos = (worldToLocal * followTargetPosition) - cameraPos;
  533. cameraOffset += OrthoOffsetToScreenBounds(
  534. realTargetPos - cameraOffset, hardGuideOrtho);
  535. }
  536. }
  537. curState.RawPosition = localToWorld * (cameraPos + cameraOffset);
  538. m_PreviousCameraPosition = curState.RawPosition;
  539. m_InheritingPosition = false;
  540. }
  541. float GetTargetHeight(Vector2 boundsSize)
  542. {
  543. switch (m_GroupFramingMode)
  544. {
  545. case FramingMode.Horizontal:
  546. return boundsSize.x / VcamState.Lens.Aspect;
  547. case FramingMode.Vertical:
  548. return boundsSize.y;
  549. default:
  550. case FramingMode.HorizontalAndVertical:
  551. return Mathf.Max(boundsSize.x / VcamState.Lens.Aspect, boundsSize.y);
  552. }
  553. }
  554. Vector3 ComputeGroupBounds(ICinemachineTargetGroup group, ref CameraState curState)
  555. {
  556. Vector3 cameraPos = curState.RawPosition;
  557. Vector3 fwd = curState.RawOrientation * Vector3.forward;
  558. // Get the bounding box from camera's direction in view space
  559. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one);
  560. Bounds b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
  561. Vector3 groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
  562. float boundsDepth = b.extents.z;
  563. if (!curState.Lens.Orthographic)
  564. {
  565. // Parallax might change bounds - refine
  566. float d = (Quaternion.Inverse(curState.RawOrientation) * (groupCenter - cameraPos)).z;
  567. cameraPos = groupCenter - fwd * (Mathf.Max(d, boundsDepth) + boundsDepth);
  568. // Will adjust cameraPos
  569. b = GetScreenSpaceGroupBoundingBox(group, ref cameraPos, curState.RawOrientation);
  570. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one);
  571. groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
  572. }
  573. LastBounds = b;
  574. return groupCenter - fwd * boundsDepth;
  575. }
  576. static Bounds GetScreenSpaceGroupBoundingBox(
  577. ICinemachineTargetGroup group, ref Vector3 pos, Quaternion orientation)
  578. {
  579. var observer = Matrix4x4.TRS(pos, orientation, Vector3.one);
  580. group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange);
  581. var shift = (minAngles + maxAngles) / 2;
  582. var q = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up);
  583. pos = q * new Vector3(0, 0, (zRange.y + zRange.x)/2);
  584. pos.z = 0;
  585. pos = observer.MultiplyPoint3x4(pos);
  586. observer = Matrix4x4.TRS(pos, orientation, Vector3.one);
  587. group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange);
  588. // For width and height (in camera space) of the bounding box, we use the values at the center of the box.
  589. // This is an arbitrary choice. The gizmo drawer will take this into account when displaying
  590. // the frustum bounds of the group
  591. var d = zRange.y + zRange.x;
  592. Vector2 angles = new Vector2(89.5f, 89.5f);
  593. if (zRange.x > 0)
  594. {
  595. angles = Vector2.Max(maxAngles, UnityVectorExtensions.Abs(minAngles));
  596. angles = Vector2.Min(angles, new Vector2(89.5f, 89.5f));
  597. }
  598. angles *= Mathf.Deg2Rad;
  599. return new Bounds(
  600. new Vector3(0, 0, d/2),
  601. new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x));
  602. }
  603. }
  604. }