| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- using UnityEngine;
- using Cinemachine.Utility;
- namespace Cinemachine
- {
- /// <summary>
- /// This is a CinemachineComponent in the Aim section of the component pipeline.
- /// Its job is to aim the camera at a target object, with configurable offsets, damping,
- /// and composition rules.
- ///
- /// In addition, if the target is a ICinemachineTargetGroup, the behaviour
- /// will adjust the FOV and the camera distance to ensure that the entire group of targets
- /// is framed properly.
- /// </summary>
- [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
- [AddComponentMenu("")] // Don't display in add component menu
- [SaveDuringPlay]
- public class CinemachineGroupComposer : CinemachineComposer
- {
- /// <summary>How much of the screen to fill with the bounding box of the targets.</summary>
- [Space]
- [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.")]
- public float m_GroupFramingSize = 0.8f;
- /// <summary>What screen dimensions to consider when framing</summary>
- [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
- public enum FramingMode
- {
- /// <summary>Consider only the horizontal dimension. Vertical framing is ignored.</summary>
- Horizontal,
- /// <summary>Consider only the vertical dimension. Horizontal framing is ignored.</summary>
- Vertical,
- /// <summary>The larger of the horizontal and vertical dimensions will dominate, to get the best fit.</summary>
- HorizontalAndVertical
- };
- /// <summary>What screen dimensions to consider when framing</summary>
- [Tooltip("What screen dimensions to consider when framing. Can be Horizontal, Vertical, or both")]
- public FramingMode m_FramingMode = FramingMode.HorizontalAndVertical;
- /// <summary>How aggressively the camera tries to frame the group.
- /// Small numbers are more responsive</summary>
- [Range(0, 20)]
- [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.")]
- public float m_FrameDamping = 2f;
- /// <summary>How to adjust the camera to get the desired framing</summary>
- public enum AdjustmentMode
- {
- /// <summary>Do not move the camera, only adjust the FOV.</summary>
- ZoomOnly,
- /// <summary>Just move the camera, don't change the FOV.</summary>
- DollyOnly,
- /// <summary>Move the camera as much as permitted by the ranges, then
- /// adjust the FOV if necessary to make the shot.</summary>
- DollyThenZoom
- };
- /// <summary>How to adjust the camera to get the desired framing</summary>
- [Tooltip("How to adjust the camera to get the desired framing. You can zoom, dolly in/out, or do both.")]
- public AdjustmentMode m_AdjustmentMode = AdjustmentMode.ZoomOnly;
- /// <summary>How much closer to the target can the camera go?</summary>
- [Tooltip("The maximum distance toward the target that this behaviour is allowed to move the camera.")]
- public float m_MaxDollyIn = 5000f;
- /// <summary>How much farther from the target can the camera go?</summary>
- [Tooltip("The maximum distance away the target that this behaviour is allowed to move the camera.")]
- public float m_MaxDollyOut = 5000f;
- /// <summary>Set this to limit how close to the target the camera can get</summary>
- [Tooltip("Set this to limit how close to the target the camera can get.")]
- public float m_MinimumDistance = 1;
- /// <summary>Set this to limit how far from the taregt the camera can get</summary>
- [Tooltip("Set this to limit how far from the target the camera can get.")]
- public float m_MaximumDistance = 5000f;
- /// <summary>If adjusting FOV, will not set the FOV lower than this</summary>
- [Range(1, 179)]
- [Tooltip("If adjusting FOV, will not set the FOV lower than this.")]
- public float m_MinimumFOV = 3;
- /// <summary>If adjusting FOV, will not set the FOV higher than this</summary>
- [Range(1, 179)]
- [Tooltip("If adjusting FOV, will not set the FOV higher than this.")]
- public float m_MaximumFOV = 60;
- /// <summary>If adjusting Orthographic Size, will not set it lower than this</summary>
- [Tooltip("If adjusting Orthographic Size, will not set it lower than this.")]
- public float m_MinimumOrthoSize = 1;
- /// <summary>If adjusting Orthographic Size, will not set it higher than this</summary>
- [Tooltip("If adjusting Orthographic Size, will not set it higher than this.")]
- public float m_MaximumOrthoSize = 5000;
- private void OnValidate()
- {
- m_GroupFramingSize = Mathf.Max(0.001f, m_GroupFramingSize);
- m_MaxDollyIn = Mathf.Max(0, m_MaxDollyIn);
- m_MaxDollyOut = Mathf.Max(0, m_MaxDollyOut);
- m_MinimumDistance = Mathf.Max(0, m_MinimumDistance);
- m_MaximumDistance = Mathf.Max(m_MinimumDistance, m_MaximumDistance);
- m_MinimumFOV = Mathf.Max(1, m_MinimumFOV);
- m_MaximumFOV = Mathf.Clamp(m_MaximumFOV, m_MinimumFOV, 179);
- m_MinimumOrthoSize = Mathf.Max(0.01f, m_MinimumOrthoSize);
- m_MaximumOrthoSize = Mathf.Max(m_MinimumOrthoSize, m_MaximumOrthoSize);
- }
- // State for damping
- float m_prevFramingDistance;
- float m_prevFOV;
- /// <summary>For editor visulaization of the calculated bounding box of the group</summary>
- public Bounds LastBounds { get; private set; }
- /// <summary>For editor visualization of the calculated bounding box of the group</summary>
- public Matrix4x4 LastBoundsMatrix { get; private set; }
- /// <summary>
- /// Report maximum damping time needed for this component.
- /// </summary>
- /// <returns>Highest damping setting in this component</returns>
- public override float GetMaxDampTime()
- {
- return Mathf.Max(base.GetMaxDampTime(), m_FrameDamping);
- }
- /// <summary>Applies the composer rules and orients the camera accordingly</summary>
- /// <param name="curState">The current camera state</param>
- /// <param name="deltaTime">Used for calculating damping. If less than
- /// zero, then target will snap to the center of the dead zone.</param>
- public override void MutateCameraState(ref CameraState curState, float deltaTime)
- {
- // Can't do anything without a group to look at
- ICinemachineTargetGroup group = AbstractLookAtTargetGroup;
- if (group == null)
- {
- base.MutateCameraState(ref curState, deltaTime);
- return;
- }
- if (!IsValid || !curState.HasLookAt)
- {
- m_prevFramingDistance = 0;
- m_prevFOV = 0;
- return;
- }
- bool isOrthographic = curState.Lens.Orthographic;
- bool canMoveCamera = !isOrthographic && m_AdjustmentMode != AdjustmentMode.ZoomOnly;
- // Get the bounding box from camera's POV in view space
- Vector3 up = curState.ReferenceUp;
- var cameraPos = curState.RawPosition;
- BoundingSphere s = group.Sphere;
- Vector3 groupCenter = s.position;
- Vector3 fwd = groupCenter - cameraPos;
- float d = fwd.magnitude;
- if (d < Epsilon)
- return; // navel-gazing, get outa here
- // Approximate looking at the group center
- fwd /= d;
- LastBoundsMatrix = Matrix4x4.TRS(
- cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
- // Correction for the actual center
- Bounds b;
- if (isOrthographic)
- {
- b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
- groupCenter = LastBoundsMatrix.MultiplyPoint3x4(b.center);
- fwd = (groupCenter - cameraPos).normalized;
- LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
- b = group.GetViewSpaceBoundingBox(LastBoundsMatrix);
- LastBounds = b;
- }
- else
- {
- b = GetScreenSpaceGroupBoundingBox(group, LastBoundsMatrix, out fwd);
- LastBoundsMatrix = Matrix4x4.TRS(cameraPos, Quaternion.LookRotation(fwd, up), Vector3.one);
- LastBounds = b;
- groupCenter = cameraPos + fwd * b.center.z;
- }
- // Adjust bounds for framing size, and get target height
- float boundsDepth = b.extents.z;
- float targetHeight = GetTargetHeight(b.size / m_GroupFramingSize);
- if (isOrthographic)
- {
- targetHeight = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize);
- // ApplyDamping
- if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
- targetHeight = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp(
- targetHeight - m_prevFOV, m_FrameDamping, deltaTime);
- m_prevFOV = targetHeight;
- LensSettings lens = curState.Lens;
- lens.OrthographicSize = Mathf.Clamp(targetHeight, m_MinimumOrthoSize, m_MaximumOrthoSize);
- curState.Lens = lens;
- }
- else
- {
- // Adjust height for perspective - we want the height at the near surface
- float z = b.center.z;
- if (z > boundsDepth)
- targetHeight = Mathf.Lerp(0, targetHeight, (z - boundsDepth) / z);
- // Move the camera
- if (canMoveCamera)
- {
- // What distance from near edge would be needed to get the adjusted
- // target height, at the current FOV
- float targetDistance = boundsDepth
- + targetHeight / (2f * Mathf.Tan(curState.Lens.FieldOfView * Mathf.Deg2Rad / 2f));
- // Clamp to respect min/max distance settings to the near surface of the bounds
- targetDistance = Mathf.Clamp(
- targetDistance, boundsDepth + m_MinimumDistance, boundsDepth + m_MaximumDistance);
- // Clamp to respect min/max camera movement
- float targetDelta = targetDistance - Vector3.Distance(curState.RawPosition, groupCenter);
- targetDelta = Mathf.Clamp(targetDelta, -m_MaxDollyIn, m_MaxDollyOut);
- // ApplyDamping
- if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
- {
- float delta = targetDelta - m_prevFramingDistance;
- delta = VirtualCamera.DetachedLookAtTargetDamp(delta, m_FrameDamping, deltaTime);
- targetDelta = m_prevFramingDistance + delta;
- }
- m_prevFramingDistance = targetDelta;
- curState.PositionCorrection -= fwd * targetDelta;
- cameraPos -= fwd * targetDelta;
- }
- // Apply zoom
- if (m_AdjustmentMode != AdjustmentMode.DollyOnly)
- {
- float nearBoundsDistance = (groupCenter - cameraPos).magnitude - boundsDepth;
- float targetFOV = 179;
- if (nearBoundsDistance > Epsilon)
- targetFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg;
- targetFOV = Mathf.Clamp(targetFOV, m_MinimumFOV, m_MaximumFOV);
- // ApplyDamping
- if (deltaTime >= 0 && m_prevFOV != 0 && VirtualCamera.PreviousStateIsValid)
- targetFOV = m_prevFOV + VirtualCamera.DetachedLookAtTargetDamp(
- targetFOV - m_prevFOV, m_FrameDamping, deltaTime);
- m_prevFOV = targetFOV;
- LensSettings lens = curState.Lens;
- lens.FieldOfView = targetFOV;
- curState.Lens = lens;
- }
- }
- // Now compose normally
- curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(
- groupCenter, curState.ReferenceUp, deltaTime);
- base.MutateCameraState(ref curState, deltaTime);
- }
- float GetTargetHeight(Vector2 boundsSize)
- {
- switch (m_FramingMode)
- {
- case FramingMode.Horizontal:
- return Mathf.Max(Epsilon, boundsSize.x ) / VcamState.Lens.Aspect;
- case FramingMode.Vertical:
- return Mathf.Max(Epsilon, boundsSize.y);
- default:
- case FramingMode.HorizontalAndVertical:
- return Mathf.Max(
- Mathf.Max(Epsilon, boundsSize.x) / VcamState.Lens.Aspect,
- Mathf.Max(Epsilon, boundsSize.y));
- }
- }
- /// <param name="observer">Point of view</param>
- /// <param name="newFwd">New forward direction to use when interpreting the return value</param>
- /// <returns>Bounding box in a slightly rotated version of observer, as specified by newFwd</returns>
- static Bounds GetScreenSpaceGroupBoundingBox(
- ICinemachineTargetGroup group, Matrix4x4 observer, out Vector3 newFwd)
- {
- group.GetViewSpaceAngularBounds(observer, out var minAngles, out var maxAngles, out var zRange);
- var shift = (minAngles + maxAngles) / 2;
- newFwd = Quaternion.identity.ApplyCameraRotation(new Vector2(-shift.x, shift.y), Vector3.up) * Vector3.forward;
- newFwd = observer.MultiplyVector(newFwd);
- // For width and height (in camera space) of the bounding box, we use the values at the center of the box.
- // This is an arbitrary choice. The gizmo drawer will take this into account when displaying
- // the frustum bounds of the group
- var d = zRange.y + zRange.x;
- var angles = Vector2.Min(maxAngles - shift, new Vector2(89.5f, 89.5f)) * Mathf.Deg2Rad;
- return new Bounds(
- new Vector3(0, 0, d/2),
- new Vector3(Mathf.Tan(angles.y) * d, Mathf.Tan(angles.x) * d, zRange.y - zRange.x));
- }
- }
- }
|