CinemachineFramingTransposer.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  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. Vector2 center = value.center;
  262. Vector2 bias = center - new Vector2(m_ScreenX, m_ScreenY);
  263. float biasWidth = Mathf.Max(0, m_SoftZoneWidth - m_DeadZoneWidth);
  264. float biasHeight = Mathf.Max(0, m_SoftZoneHeight - m_DeadZoneHeight);
  265. m_BiasX = biasWidth < Epsilon ? 0 : Mathf.Clamp(bias.x / biasWidth, -0.5f, 0.5f);
  266. m_BiasY = biasHeight < Epsilon ? 0 : Mathf.Clamp(bias.y / biasHeight, -0.5f, 0.5f);
  267. }
  268. }
  269. private void OnValidate()
  270. {
  271. m_CameraDistance = Mathf.Max(m_CameraDistance, kMinimumCameraDistance);
  272. m_DeadZoneDepth = Mathf.Max(m_DeadZoneDepth, 0);
  273. m_GroupFramingSize = Mathf.Max(0.001f, m_GroupFramingSize);
  274. m_MaxDollyIn = Mathf.Max(0, m_MaxDollyIn);
  275. m_MaxDollyOut = Mathf.Max(0, m_MaxDollyOut);
  276. m_MinimumDistance = Mathf.Max(0, m_MinimumDistance);
  277. m_MaximumDistance = Mathf.Max(m_MinimumDistance, m_MaximumDistance);
  278. m_MinimumFOV = Mathf.Max(1, m_MinimumFOV);
  279. m_MaximumFOV = Mathf.Clamp(m_MaximumFOV, m_MinimumFOV, 179);
  280. m_MinimumOrthoSize = Mathf.Max(0.01f, m_MinimumOrthoSize);
  281. m_MaximumOrthoSize = Mathf.Max(m_MinimumOrthoSize, m_MaximumOrthoSize);
  282. }
  283. /// <summary>True if component is enabled and has a valid Follow target</summary>
  284. public override bool IsValid { get { return enabled && FollowTarget != null; } }
  285. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  286. /// Always returns the Body stage</summary>
  287. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  288. /// <summary>FramingTransposer's algorithm tahes camera orientation as input,
  289. /// so even though it is a Body component, it must apply after Aim</summary>
  290. public override bool BodyAppliesAfterAim { get { return true; } }
  291. const float kMinimumCameraDistance = 0.01f;
  292. const float kMinimumGroupSize = 0.01f;
  293. /// <summary>State information for damping</summary>
  294. Vector3 m_PreviousCameraPosition = Vector3.zero;
  295. PositionPredictor m_Predictor = new PositionPredictor();
  296. /// <summary>Internal API for inspector</summary>
  297. public Vector3 TrackedPoint { get; private set; }
  298. /// <summary>This is called to notify the us that a target got warped,
  299. /// so that we can update its internal state to make the camera
  300. /// also warp seamlessy.</summary>
  301. /// <param name="target">The object that was warped</param>
  302. /// <param name="positionDelta">The amount the target's position changed</param>
  303. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  304. {
  305. base.OnTargetObjectWarped(target, positionDelta);
  306. if (target == FollowTarget)
  307. {
  308. m_PreviousCameraPosition += positionDelta;
  309. m_Predictor.ApplyTransformDelta(positionDelta);
  310. }
  311. }
  312. /// <summary>
  313. /// Force the virtual camera to assume a given position and orientation
  314. /// </summary>
  315. /// <param name="pos">Worldspace pposition to take</param>
  316. /// <param name="rot">Worldspace orientation to take</param>
  317. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  318. {
  319. base.ForceCameraPosition(pos, rot);
  320. m_PreviousCameraPosition = pos;
  321. m_prevRotation = rot;
  322. }
  323. /// <summary>
  324. /// Report maximum damping time needed for this component.
  325. /// </summary>
  326. /// <returns>Highest damping setting in this component</returns>
  327. public override float GetMaxDampTime()
  328. {
  329. return Mathf.Max(m_XDamping, Mathf.Max(m_YDamping, m_ZDamping));
  330. }
  331. /// <summary>Notification that this virtual camera is going live.
  332. /// Base class implementation does nothing.</summary>
  333. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  334. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  335. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  336. /// <param name="transitionParams">Transition settings for this vcam</param>
  337. /// <returns>True if the vcam should do an internal update as a result of this call</returns>
  338. public override bool OnTransitionFromCamera(
  339. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime,
  340. ref CinemachineVirtualCameraBase.TransitionParams transitionParams)
  341. {
  342. if (fromCam != null && transitionParams.m_InheritPosition)
  343. {
  344. m_PreviousCameraPosition = fromCam.State.RawPosition;
  345. m_prevRotation = fromCam.State.RawOrientation;
  346. InheritingPosition = true;
  347. return true;
  348. }
  349. return false;
  350. }
  351. bool InheritingPosition { get; set; }
  352. // Convert from screen coords to normalized orthographic distance coords
  353. private Rect ScreenToOrtho(Rect rScreen, float orthoSize, float aspect)
  354. {
  355. Rect r = new Rect();
  356. r.yMax = 2 * orthoSize * ((1f-rScreen.yMin) - 0.5f);
  357. r.yMin = 2 * orthoSize * ((1f-rScreen.yMax) - 0.5f);
  358. r.xMin = 2 * orthoSize * aspect * (rScreen.xMin - 0.5f);
  359. r.xMax = 2 * orthoSize * aspect * (rScreen.xMax - 0.5f);
  360. return r;
  361. }
  362. private Vector3 OrthoOffsetToScreenBounds(Vector3 targetPos2D, Rect screenRect)
  363. {
  364. // Bring it to the edge of screenRect, if outside. Leave it alone if inside.
  365. Vector3 delta = Vector3.zero;
  366. if (targetPos2D.x < screenRect.xMin)
  367. delta.x += targetPos2D.x - screenRect.xMin;
  368. if (targetPos2D.x > screenRect.xMax)
  369. delta.x += targetPos2D.x - screenRect.xMax;
  370. if (targetPos2D.y < screenRect.yMin)
  371. delta.y += targetPos2D.y - screenRect.yMin;
  372. if (targetPos2D.y > screenRect.yMax)
  373. delta.y += targetPos2D.y - screenRect.yMax;
  374. return delta;
  375. }
  376. float m_prevFOV; // State for frame damping
  377. Quaternion m_prevRotation;
  378. /// <summary>For editor visulaization of the calculated bounding box of the group</summary>
  379. public Bounds LastBounds { get; private set; }
  380. /// <summary>For editor visualization of the calculated bounding box of the group</summary>
  381. public Matrix4x4 LastBoundsMatrix { get; private set; }
  382. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  383. /// <param name="curState">The current camera state</param>
  384. /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
  385. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  386. {
  387. LensSettings lens = curState.Lens;
  388. Vector3 followTargetPosition = FollowTargetPosition + (FollowTargetRotation * m_TrackedObjectOffset);
  389. bool previousStateIsValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
  390. if (!previousStateIsValid)
  391. {
  392. m_Predictor.Reset();
  393. m_PreviousCameraPosition = curState.RawPosition;
  394. m_prevFOV = lens.Orthographic ? lens.OrthographicSize : lens.FieldOfView;
  395. m_prevRotation = curState.RawOrientation;
  396. if (!InheritingPosition && m_CenterOnActivate)
  397. {
  398. m_PreviousCameraPosition = FollowTargetPosition
  399. + (curState.RawOrientation * Vector3.back) * m_CameraDistance;
  400. }
  401. }
  402. if (!IsValid)
  403. {
  404. InheritingPosition = false;
  405. return;
  406. }
  407. // Compute group bounds and adjust follow target for group framing
  408. ICinemachineTargetGroup group = AbstractFollowTargetGroup;
  409. bool isGroupFraming = group != null && m_GroupFramingMode != FramingMode.None && !group.IsEmpty;
  410. if (isGroupFraming)
  411. followTargetPosition = ComputeGroupBounds(group, ref curState);
  412. TrackedPoint = followTargetPosition;
  413. if (m_LookaheadTime > Epsilon)
  414. {
  415. m_Predictor.Smoothing = m_LookaheadSmoothing;
  416. m_Predictor.AddPosition(followTargetPosition, deltaTime, m_LookaheadTime);
  417. var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime);
  418. if (m_LookaheadIgnoreY)
  419. delta = delta.ProjectOntoPlane(curState.ReferenceUp);
  420. var p = followTargetPosition + delta;
  421. if (isGroupFraming)
  422. {
  423. var b = LastBounds;
  424. b.center += LastBoundsMatrix.MultiplyPoint3x4(delta);
  425. LastBounds = b;
  426. }
  427. TrackedPoint = p;
  428. }
  429. if (!curState.HasLookAt)
  430. curState.ReferenceLookAt = followTargetPosition;
  431. // Adjust the desired depth for group framing
  432. float targetDistance = m_CameraDistance;
  433. bool isOrthographic = lens.Orthographic;
  434. float targetHeight = isGroupFraming ? GetTargetHeight(LastBounds.size / m_GroupFramingSize) : 0;
  435. targetHeight = Mathf.Max(targetHeight, kMinimumGroupSize);
  436. if (!isOrthographic && isGroupFraming)
  437. {
  438. // Adjust height for perspective - we want the height at the near surface
  439. float boundsDepth = LastBounds.extents.z;
  440. float z = LastBounds.center.z;
  441. if (z > boundsDepth)
  442. targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z);
  443. if (m_AdjustmentMode != AdjustmentMode.ZoomOnly)
  444. {
  445. // What distance from near edge would be needed to get the adjusted
  446. // target height, at the current FOV
  447. targetDistance = targetHeight / (2f * Mathf.Tan(lens.FieldOfView * Mathf.Deg2Rad / 2f));
  448. // Clamp to respect min/max distance settings to the near surface of the bounds
  449. targetDistance = Mathf.Clamp(targetDistance, m_MinimumDistance, m_MaximumDistance);
  450. // Clamp to respect min/max camera movement
  451. float targetDelta = targetDistance - m_CameraDistance;
  452. targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut);
  453. targetDistance = m_CameraDistance + targetDelta;
  454. }
  455. }
  456. // Optionally allow undamped camera orientation change
  457. Quaternion localToWorld = curState.RawOrientation;
  458. if (previousStateIsValid && m_TargetMovementOnly)
  459. {
  460. var q = localToWorld * Quaternion.Inverse(m_prevRotation);
  461. m_PreviousCameraPosition = TrackedPoint + q * (m_PreviousCameraPosition - TrackedPoint);
  462. }
  463. m_prevRotation = localToWorld;
  464. // Work in camera-local space
  465. Vector3 camPosWorld = m_PreviousCameraPosition;
  466. Quaternion worldToLocal = Quaternion.Inverse(localToWorld);
  467. Vector3 cameraPos = worldToLocal * camPosWorld;
  468. Vector3 targetPos = (worldToLocal * TrackedPoint) - cameraPos;
  469. Vector3 lookAtPos = targetPos;
  470. // Move along camera z
  471. Vector3 cameraOffset = Vector3.zero;
  472. float cameraMin = Mathf.Max(kMinimumCameraDistance, targetDistance - m_DeadZoneDepth/2);
  473. float cameraMax = Mathf.Max(cameraMin, targetDistance + m_DeadZoneDepth/2);
  474. float targetZ = Mathf.Min(targetPos.z, lookAtPos.z);
  475. if (targetZ < cameraMin)
  476. cameraOffset.z = targetZ - cameraMin;
  477. if (targetZ > cameraMax)
  478. cameraOffset.z = targetZ - cameraMax;
  479. // Move along the XY plane
  480. float screenSize = lens.Orthographic
  481. ? lens.OrthographicSize
  482. : Mathf.Tan(0.5f * lens.FieldOfView * Mathf.Deg2Rad) * (targetZ - cameraOffset.z);
  483. Rect softGuideOrtho = ScreenToOrtho(SoftGuideRect, screenSize, lens.Aspect);
  484. if (!previousStateIsValid)
  485. {
  486. // No damping or hard bounds, just snap to central bounds, skipping the soft zone
  487. Rect rect = softGuideOrtho;
  488. if (m_CenterOnActivate && !InheritingPosition)
  489. rect = new Rect(rect.center, Vector2.zero); // Force to center
  490. cameraOffset += OrthoOffsetToScreenBounds(targetPos, rect);
  491. }
  492. else
  493. {
  494. // Move it through the soft zone, with damping
  495. cameraOffset += OrthoOffsetToScreenBounds(targetPos, softGuideOrtho);
  496. cameraOffset = VirtualCamera.DetachedFollowTargetDamp(
  497. cameraOffset, new Vector3(m_XDamping, m_YDamping, m_ZDamping), deltaTime);
  498. // Make sure the real target (not the lookahead one) is still in the frame
  499. if (!m_UnlimitedSoftZone
  500. && (deltaTime < 0 || VirtualCamera.FollowTargetAttachment > 1 - Epsilon))
  501. {
  502. Rect hardGuideOrtho = ScreenToOrtho(HardGuideRect, screenSize, lens.Aspect);
  503. var realTargetPos = (worldToLocal * followTargetPosition) - cameraPos;
  504. cameraOffset += OrthoOffsetToScreenBounds(
  505. realTargetPos - cameraOffset, hardGuideOrtho);
  506. }
  507. }
  508. curState.RawPosition = localToWorld * (cameraPos + cameraOffset);
  509. m_PreviousCameraPosition = curState.RawPosition;
  510. // Adjust lens for group framing
  511. if (isGroupFraming)
  512. {
  513. if (isOrthographic)
  514. {
  515. targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize);
  516. // Apply Damping
  517. if (previousStateIsValid)
  518. targetHeight = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp(
  519. targetHeight - m_prevFOV, m_ZDamping, deltaTime);
  520. m_prevFOV = targetHeight;
  521. lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize);
  522. curState.Lens = lens;
  523. }
  524. else if (m_AdjustmentMode != AdjustmentMode.DollyOnly)
  525. {
  526. var localTarget = Quaternion.Inverse(curState.RawOrientation)
  527. * (followTargetPosition - curState.RawPosition);
  528. float nearBoundsDistance = localTarget.z;
  529. float targetFOV = 179;
  530. if (nearBoundsDistance > Epsilon)
  531. targetFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg;
  532. targetFOV = Mathf.Clamp(targetFOV, m_MinimumFOV, m_MaximumFOV);
  533. // ApplyDamping
  534. if (previousStateIsValid)
  535. targetFOV = m_prevFOV + VirtualCamera.DetachedFollowTargetDamp(
  536. targetFOV - m_prevFOV, m_ZDamping, deltaTime);
  537. m_prevFOV = targetFOV;
  538. lens.FieldOfView = targetFOV;
  539. curState.Lens = lens;
  540. }
  541. }
  542. InheritingPosition = false;
  543. }
  544. float GetTargetHeight(Vector2 boundsSize)
  545. {
  546. switch (m_GroupFramingMode)
  547. {
  548. case FramingMode.Horizontal:
  549. return boundsSize.x / VcamState.Lens.Aspect;
  550. case FramingMode.Vertical:
  551. return boundsSize.y;
  552. default:
  553. case FramingMode.HorizontalAndVertical:
  554. return Mathf.Max(boundsSize.x / VcamState.Lens.Aspect, boundsSize.y);
  555. }
  556. }
  557. Vector3 ComputeGroupBounds(ICinemachineTargetGroup group, ref CameraState curState)
  558. {
  559. Vector3 cameraPos = curState.RawPosition;
  560. Vector3 fwd = curState.RawOrientation * Vector3.forward;
  561. // Get the bounding box from camera's direction in view space
  562. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one);
  563. Bounds b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
  564. Vector3 groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
  565. float boundsDepth = b.extents.z;
  566. if (!curState.Lens.Orthographic)
  567. {
  568. // Parallax might change bounds - refine
  569. float d = (Quaternion.Inverse(curState.RawOrientation) * (groupCenter - cameraPos)).z;
  570. cameraPos = groupCenter - fwd * (Mathf.Max(d, boundsDepth) + boundsDepth);
  571. // Will adjust cameraPos
  572. b = GetScreenSpaceGroupBoundingBox(group, ref cameraPos, curState.RawOrientation);
  573. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, curState.RawOrientation, Vector3.one);
  574. groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
  575. }
  576. LastBounds = b;
  577. return groupCenter - fwd * boundsDepth;
  578. }
  579. static Bounds GetScreenSpaceGroupBoundingBox(
  580. ICinemachineTargetGroup group, ref Vector3 pos, Quaternion orientation)
  581. {
  582. var observer = Matrix4x4.TRS(pos, orientation, Vector3.one);
  583. group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange);
  584. var shift = (minAngles + maxAngles) / 2;
  585. var q = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up);
  586. pos = q * new Vector3(0, 0, (zRange.y + zRange.x)/2);
  587. pos.z = 0;
  588. pos = observer.MultiplyPoint3x4(pos);
  589. observer = Matrix4x4.TRS(pos, orientation, Vector3.one);
  590. group.GetViewSpaceAngularBounds(observer, out minAngles, out maxAngles, out zRange);
  591. // For width and height (in camera space) of the bounding box, we use the values at the center of the box.
  592. // This is an arbitrary choice. The gizmo drawer will take this into account when displaying
  593. // the frustum bounds of the group
  594. var d = zRange.y + zRange.x;
  595. Vector2 angles = new Vector2(89.5f, 89.5f);
  596. if (zRange.x > 0)
  597. {
  598. angles = Vector2.Max(maxAngles, UnityVectorExtensions.Abs(minAngles));
  599. angles = Vector2.Min(angles, new Vector2(89.5f, 89.5f));
  600. }
  601. angles *= Mathf.Deg2Rad;
  602. return new Bounds(
  603. new Vector3(0, 0, d/2),
  604. new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x));
  605. }
  606. }
  607. }