CinemachineGroupComposer.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using UnityEngine;
  2. using Cinemachine.Utility;
  3. namespace Cinemachine
  4. {
  5. /// <summary>
  6. /// This is a CinemachineComponent in the Aim section of the component pipeline.
  7. /// Its job is to aim the camera at a target object, with configurable offsets, damping,
  8. /// and composition rules.
  9. ///
  10. /// In addition, if the target is a ICinemachineTargetGroup, the behaviour
  11. /// will adjust the FOV and the camera distance to ensure that the entire group of targets
  12. /// is framed properly.
  13. /// </summary>
  14. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  15. [AddComponentMenu("")] // Don't display in add component menu
  16. [SaveDuringPlay]
  17. public class CinemachineGroupComposer : CinemachineComposer
  18. {
  19. /// <summary>How much of the screen to fill with the bounding box of the targets.</summary>
  20. [Space]
  21. [Tooltip("The bounding box of the targets should occupy this amount of the screen space. 1 means fill the whole screen. 0.5 means fill half the screen, etc.")]
  22. public float m_GroupFramingSize = 0.8f;
  23. /// <summary>What screen dimensions to consider when framing</summary>
  24. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  25. public enum FramingMode
  26. {
  27. /// <summary>Consider only the horizontal dimension. Vertical framing is ignored.</summary>
  28. Horizontal,
  29. /// <summary>Consider only the vertical dimension. Horizontal framing is ignored.</summary>
  30. Vertical,
  31. /// <summary>The larger of the horizontal and vertical dimensions will dominate, to get the best fit.</summary>
  32. HorizontalAndVertical
  33. };
  34. /// <summary>What screen dimensions to consider when framing</summary>
  35. [Tooltip("What screen dimensions to consider when framing. Can be Horizontal, Vertical, or both")]
  36. public FramingMode m_FramingMode = FramingMode.HorizontalAndVertical;
  37. /// <summary>How aggressively the camera tries to frame the group.
  38. /// Small numbers are more responsive</summary>
  39. [Range(0, 20)]
  40. [Tooltip("How aggressively the camera tries to frame the group. Small numbers are more responsive, rapidly adjusting the camera to keep the group in the frame. Larger numbers give a more heavy slowly responding camera.")]
  41. public float m_FrameDamping = 2f;
  42. /// <summary>How to adjust the camera to get the desired framing</summary>
  43. public enum AdjustmentMode
  44. {
  45. /// <summary>Do not move the camera, only adjust the FOV.</summary>
  46. ZoomOnly,
  47. /// <summary>Just move the camera, don't change the FOV.</summary>
  48. DollyOnly,
  49. /// <summary>Move the camera as much as permitted by the ranges, then
  50. /// adjust the FOV if necessary to make the shot.</summary>
  51. DollyThenZoom
  52. };
  53. /// <summary>How to adjust the camera to get the desired framing</summary>
  54. [Tooltip("How to adjust the camera to get the desired framing. You can zoom, dolly in/out, or do both.")]
  55. public AdjustmentMode m_AdjustmentMode = AdjustmentMode.ZoomOnly;
  56. /// <summary>How much closer to the target can the camera go?</summary>
  57. [Tooltip("The maximum distance toward the target that this behaviour is allowed to move the camera.")]
  58. public float m_MaxDollyIn = 5000f;
  59. /// <summary>How much farther from the target can the camera go?</summary>
  60. [Tooltip("The maximum distance away the target that this behaviour is allowed to move the camera.")]
  61. public float m_MaxDollyOut = 5000f;
  62. /// <summary>Set this to limit how close to the target the camera can get</summary>
  63. [Tooltip("Set this to limit how close to the target the camera can get.")]
  64. public float m_MinimumDistance = 1;
  65. /// <summary>Set this to limit how far from the taregt the camera can get</summary>
  66. [Tooltip("Set this to limit how far from the target the camera can get.")]
  67. public float m_MaximumDistance = 5000f;
  68. /// <summary>If adjusting FOV, will not set the FOV lower than this</summary>
  69. [Range(1, 179)]
  70. [Tooltip("If adjusting FOV, will not set the FOV lower than this.")]
  71. public float m_MinimumFOV = 3;
  72. /// <summary>If adjusting FOV, will not set the FOV higher than this</summary>
  73. [Range(1, 179)]
  74. [Tooltip("If adjusting FOV, will not set the FOV higher than this.")]
  75. public float m_MaximumFOV = 60;
  76. /// <summary>If adjusting Orthographic Size, will not set it lower than this</summary>
  77. [Tooltip("If adjusting Orthographic Size, will not set it lower than this.")]
  78. public float m_MinimumOrthoSize = 1;
  79. /// <summary>If adjusting Orthographic Size, will not set it higher than this</summary>
  80. [Tooltip("If adjusting Orthographic Size, will not set it higher than this.")]
  81. public float m_MaximumOrthoSize = 5000;
  82. private void OnValidate()
  83. {
  84. m_GroupFramingSize = Mathf.Max(0.001f, m_GroupFramingSize);
  85. m_MaxDollyIn = Mathf.Max(0, m_MaxDollyIn);
  86. m_MaxDollyOut = Mathf.Max(0, m_MaxDollyOut);
  87. m_MinimumDistance = Mathf.Max(0, m_MinimumDistance);
  88. m_MaximumDistance = Mathf.Max(m_MinimumDistance, m_MaximumDistance);
  89. m_MinimumFOV = Mathf.Max(1, m_MinimumFOV);
  90. m_MaximumFOV = Mathf.Clamp(m_MaximumFOV, m_MinimumFOV, 179);
  91. m_MinimumOrthoSize = Mathf.Max(0.01f, m_MinimumOrthoSize);
  92. m_MaximumOrthoSize = Mathf.Max(m_MinimumOrthoSize, m_MaximumOrthoSize);
  93. }
  94. // State for damping
  95. float m_prevFramingDistance;
  96. float m_prevFOV;
  97. /// <summary>For editor visulaization of the calculated bounding box of the group</summary>
  98. public Bounds LastBounds { get; private set; }
  99. /// <summary>For editor visualization of the calculated bounding box of the group</summary>
  100. public Matrix4x4 LastBoundsMatrix { get; private set; }
  101. /// <summary>
  102. /// Report maximum damping time needed for this component.
  103. /// </summary>
  104. /// <returns>Highest damping setting in this component</returns>
  105. public override float GetMaxDampTime()
  106. {
  107. return Mathf.Max(base.GetMaxDampTime(), m_FrameDamping);
  108. }
  109. /// <summary>Applies the composer rules and orients the camera accordingly</summary>
  110. /// <param name="curState">The current camera state</param>
  111. /// <param name="deltaTime">Used for calculating damping. If less than
  112. /// zero, then target will snap to the center of the dead zone.</param>
  113. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  114. {
  115. // Can't do anything without a group to look at
  116. ICinemachineTargetGroup group = AbstractLookAtTargetGroup;
  117. if (group == null)
  118. {
  119. base.MutateCameraState(ref curState, deltaTime);
  120. return;
  121. }
  122. if (!IsValid || !curState.HasLookAt)
  123. {
  124. m_prevFramingDistance = 0;
  125. m_prevFOV = 0;
  126. return;
  127. }
  128. bool isOrthographic = curState.Lens.Orthographic;
  129. bool canMoveCamera = !isOrthographic && m_AdjustmentMode != AdjustmentMode.ZoomOnly;
  130. // Get the bounding box from camera's POV in view space
  131. Vector3 up = curState.ReferenceUp;
  132. var cameraPos = curState.RawPosition;
  133. BoundingSphere s = group.Sphere;
  134. Vector3 groupCenter = s.position;
  135. Vector3 fwd = groupCenter - cameraPos;
  136. float d = fwd.magnitude;
  137. if (d < Epsilon)
  138. return; // navel-gazing, get outa here
  139. // Approximate looking at the group center
  140. fwd /= d;
  141. LastBoundsMatrix = Matrix4x4.TRS(
  142. cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
  143. // Correction for the actual center
  144. Bounds b;
  145. if (isOrthographic)
  146. {
  147. b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
  148. groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
  149. fwd = (groupCenter - cameraPos).normalized;
  150. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
  151. b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
  152. LastBounds = b;
  153. }
  154. else
  155. {
  156. b = GetScreenSpaceGroupBoundingBox(group, LastBoundsMatrix, out fwd);
  157. LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
  158. LastBounds = b;
  159. groupCenter = cameraPos + fwd * b.center.z;
  160. }
  161. // Adjust bounds for framing size, and get target height
  162. float boundsDepth = b.extents.z;
  163. float targetHeight = GetTargetHeight(b.size / m_GroupFramingSize);
  164. if (isOrthographic)
  165. {
  166. targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize);
  167. // ApplyDamping
  168. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  169. targetHeight = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp(
  170. targetHeight - m_prevFOV, m_FrameDamping, deltaTime);
  171. m_prevFOV = targetHeight;
  172. LensSettings lens = curState.Lens;
  173. lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize);
  174. curState.Lens = lens;
  175. }
  176. else
  177. {
  178. // Adjust height for perspective - we want the height at the near surface
  179. float z = b.center.z;
  180. if (z > boundsDepth)
  181. targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z);
  182. // Move the camera
  183. if (canMoveCamera)
  184. {
  185. // What distance from near edge would be needed to get the adjusted
  186. // target height, at the current FOV
  187. float targetDistance = boundsDepth
  188. + targetHeight / (2f * Mathf.Tan(curState.Lens.FieldOfView * Mathf.Deg2Rad / 2f));
  189. // Clamp to respect min/max distance settings to the near surface of the bounds
  190. targetDistance = Mathf.Clamp(
  191. targetDistance, boundsDepth + m_MinimumDistance, boundsDepth + m_MaximumDistance);
  192. // Clamp to respect min/max camera movement
  193. float targetDelta = targetDistance - Vector3.Distance(curState.RawPosition, groupCenter);
  194. targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut);
  195. // ApplyDamping
  196. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  197. {
  198. float delta = targetDelta - m_prevFramingDistance;
  199. delta = VirtualCamera.DetachedLookAtTargetDamp(delta, m_FrameDamping, deltaTime);
  200. targetDelta = m_prevFramingDistance + delta;
  201. }
  202. m_prevFramingDistance = targetDelta;
  203. curState.PositionCorrection -= fwd * targetDelta;
  204. cameraPos -= fwd * targetDelta;
  205. }
  206. // Apply zoom
  207. if (m_AdjustmentMode != AdjustmentMode.DollyOnly)
  208. {
  209. float nearBoundsDistance = (groupCenter - cameraPos).magnitude - boundsDepth;
  210. float targetFOV = 179;
  211. if (nearBoundsDistance > Epsilon)
  212. targetFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg;
  213. targetFOV = Mathf.Clamp(targetFOV, m_MinimumFOV, m_MaximumFOV);
  214. // ApplyDamping
  215. if (deltaTime >= 0 && m_prevFOV != 0 && VirtualCamera.PreviousStateIsValid)
  216. targetFOV = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp(
  217. targetFOV - m_prevFOV, m_FrameDamping, deltaTime);
  218. m_prevFOV = targetFOV;
  219. LensSettings lens = curState.Lens;
  220. lens.FieldOfView = targetFOV;
  221. curState.Lens = lens;
  222. }
  223. }
  224. // Now compose normally
  225. curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(
  226. groupCenter, curState.ReferenceUp, deltaTime);
  227. base.MutateCameraState(ref curState, deltaTime);
  228. }
  229. float GetTargetHeight(Vector2 boundsSize)
  230. {
  231. switch (m_FramingMode)
  232. {
  233. case FramingMode.Horizontal:
  234. return Mathf.Max(Epsilon, boundsSize.x ) / VcamState.Lens.Aspect;
  235. case FramingMode.Vertical:
  236. return Mathf.Max(Epsilon, boundsSize.y);
  237. default:
  238. case FramingMode.HorizontalAndVertical:
  239. return Mathf.Max(
  240. Mathf.Max(Epsilon, boundsSize.x) / VcamState.Lens.Aspect,
  241. Mathf.Max(Epsilon, boundsSize.y));
  242. }
  243. }
  244. /// <param name="observer">Point of view</param>
  245. /// <param name="newFwd">New forward direction to use when interpreting the return value</param>
  246. /// <returns>Bounding box in a slightly rotated version of observer, as specified by newFwd</returns>
  247. static Bounds GetScreenSpaceGroupBoundingBox(
  248. ICinemachineTargetGroup group, Matrix4x4 observer, out Vector3 newFwd)
  249. {
  250. group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange);
  251. var shift = (minAngles + maxAngles) / 2;
  252. newFwd = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up) * Vector3.forward;
  253. newFwd = observer.MultiplyVector(newFwd);
  254. // For width and height (in camera space) of the bounding box, we use the values at the center of the box.
  255. // This is an arbitrary choice. The gizmo drawer will take this into account when displaying
  256. // the frustum bounds of the group
  257. var d = zRange.y + zRange.x;
  258. var angles = Vector2.Min(maxAngles - shift, new Vector2(89.5f, 89.5f)) * Mathf.Deg2Rad;
  259. return new Bounds(
  260. new Vector3(0, 0, d/2),
  261. new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x));
  262. }
  263. }
  264. }