| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- using System;
- using Cinemachine.Utility;
- using UnityEngine;
- namespace Cinemachine
- {
- /// <summary>
- /// This is a CinemachineComponent in the Body section of the component pipeline.
- /// Its job is to position the camera in a fixed relationship to the vcam's Follow
- /// target object, with offsets and damping.
- ///
- /// The Tansposer will only change the camera's position in space. It will not
- /// re-orient or otherwise aim the camera. To to that, you need to instruct
- /// the vcam in the Aim section of its pipeline.
- /// </summary>
- [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
- [AddComponentMenu("")] // Don't display in add component menu
- [SaveDuringPlay]
- public class CinemachineTransposer : CinemachineComponentBase
- {
- /// <summary>
- /// The coordinate space to use when interpreting the offset from the target
- /// </summary>
- [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
- public enum BindingMode
- {
- /// <summary>
- /// Camera will be bound to the Follow target using a frame of reference consisting
- /// of the target's local frame at the moment when the virtual camera was enabled,
- /// or when the target was assigned.
- /// </summary>
- LockToTargetOnAssign = 0,
- /// <summary>
- /// Camera will be bound to the Follow target using a frame of reference consisting
- /// of the target's local frame, with the tilt and roll zeroed out.
- /// </summary>
- LockToTargetWithWorldUp = 1,
- /// <summary>
- /// Camera will be bound to the Follow target using a frame of reference consisting
- /// of the target's local frame, with the roll zeroed out.
- /// </summary>
- LockToTargetNoRoll = 2,
- /// <summary>
- /// Camera will be bound to the Follow target using the target's local frame.
- /// </summary>
- LockToTarget = 3,
- /// <summary>Camera will be bound to the Follow target using a world space offset.</summary>
- WorldSpace = 4,
- /// <summary>Offsets will be calculated relative to the target, using Camera-local axes</summary>
- SimpleFollowWithWorldUp = 5
- }
- /// <summary>The coordinate space to use when interpreting the offset from the target</summary>
- [Tooltip("The coordinate space to use when interpreting the offset from the target. This is also "
- + "used to set the camera's Up vector, which will be maintained when aiming the camera.")]
- public BindingMode m_BindingMode = BindingMode.LockToTargetWithWorldUp;
- /// <summary>The distance which the transposer will attempt to maintain from the transposer subject</summary>
- [Tooltip("The distance vector that the transposer will attempt to maintain from the Follow target")]
- public Vector3 m_FollowOffset = Vector3.back * 10f;
- /// <summary>How aggressively the camera tries to maintain the offset in the X-axis.
- /// Small numbers are more responsive, rapidly translating the camera to keep the target's
- /// x-axis offset. Larger numbers give a more heavy slowly responding camera.
- /// Using different settings per axis can yield a wide range of camera behaviors</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to maintain the offset in the X-axis. Small numbers "
- + "are more responsive, rapidly translating the camera to keep the target's x-axis offset. "
- + "Larger numbers give a more heavy slowly responding camera. Using different settings per "
- + "axis can yield a wide range of camera behaviors.")]
- public float m_XDamping = 1f;
- /// <summary>How aggressively the camera tries to maintain the offset in the Y-axis.
- /// Small numbers are more responsive, rapidly translating the camera to keep the target's
- /// y-axis offset. Larger numbers give a more heavy slowly responding camera.
- /// Using different settings per axis can yield a wide range of camera behaviors</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to maintain the offset in the Y-axis. Small numbers "
- + "are more responsive, rapidly translating the camera to keep the target's y-axis offset. "
- + "Larger numbers give a more heavy slowly responding camera. Using different settings per "
- + "axis can yield a wide range of camera behaviors.")]
- public float m_YDamping = 1f;
- /// <summary>How aggressively the camera tries to maintain the offset in the Z-axis.
- /// Small numbers are more responsive, rapidly translating the camera to keep the
- /// target's z-axis offset. Larger numbers give a more heavy slowly responding camera.
- /// Using different settings per axis can yield a wide range of camera behaviors</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to maintain the offset in the Z-axis. "
- + "Small numbers are more responsive, rapidly translating the camera to keep the "
- + "target's z-axis offset. Larger numbers give a more heavy slowly responding camera. "
- + "Using different settings per axis can yield a wide range of camera behaviors.")]
- public float m_ZDamping = 1f;
- /// <summary>How to calculate the angular damping for the target orientation</summary>
- public enum AngularDampingMode
- {
- /// <summary>Use Euler angles to specify damping values.
- /// Subject to gimbal-lock fwhen pitch is steep.</summary>
- Euler,
- /// <summary>
- /// Use quaternions to calculate angular damping.
- /// No per-channel control, but not susceptible to gimbal-lock</summary>
- Quaternion
- }
- /// <summary>How to calculate the angular damping for the target orientation.
- /// Use Quaternion if you expect the target to take on very steep pitches, which would
- /// be subject to gimbal lock if Eulers are used.</summary>
- public AngularDampingMode m_AngularDampingMode = AngularDampingMode.Euler;
- /// <summary>How aggressively the camera tries to track the target rotation's X angle.
- /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to track the target rotation's X angle. "
- + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
- public float m_PitchDamping = 0;
- /// <summary>How aggressively the camera tries to track the target rotation's Y angle.
- /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to track the target rotation's Y angle. "
- + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
- public float m_YawDamping = 0;
- /// <summary>How aggressively the camera tries to track the target rotation's Z angle.
- /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to track the target rotation's Z angle. "
- + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
- public float m_RollDamping = 0f;
- /// <summary>How aggressively the camera tries to track the target's orientation.
- /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
- [Range(0f, 20f)]
- [Tooltip("How aggressively the camera tries to track the target's orientation. "
- + "Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.")]
- public float m_AngularDamping = 0f;
- /// <summary>Derived classes should call this from their OnValidate() implementation</summary>
- protected virtual void OnValidate()
- {
- m_FollowOffset = EffectiveOffset;
- }
- /// <summary>Hide the offset in int inspector. Used by FreeLook.</summary>
- public bool HideOffsetInInspector { get; set; }
- /// <summary>Get the target offset, with sanitization</summary>
- public Vector3 EffectiveOffset
- {
- get
- {
- Vector3 offset = m_FollowOffset;
- if (m_BindingMode == BindingMode.SimpleFollowWithWorldUp)
- {
- offset.x = 0;
- offset.z = -Mathf.Abs(offset.z);
- }
- return offset;
- }
- }
- /// <summary>True if component is enabled and has a valid Follow target</summary>
- public override bool IsValid { get { return enabled && FollowTarget != null; } }
- /// <summary>Get the Cinemachine Pipeline stage that this component implements.
- /// Always returns the Body stage</summary>
- public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
- /// <summary>
- /// Report maximum damping time needed for this component.
- /// </summary>
- /// <returns>Highest damping setting in this component</returns>
- public override float GetMaxDampTime()
- {
- var d = Damping;
- var d2 = AngularDamping;
- var a = Mathf.Max(d.x, Mathf.Max(d.y, d.z));
- var b = Mathf.Max(d2.x, Mathf.Max(d2.y, d2.z));
- return Mathf.Max(a, b);
- }
- /// <summary>Positions the virtual camera according to the transposer rules.</summary>
- /// <param name="curState">The current camera state</param>
- /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
- public override void MutateCameraState(ref CameraState curState, float deltaTime)
- {
- InitPrevFrameStateInfo(ref curState, deltaTime);
- if (IsValid)
- {
- Vector3 offset = EffectiveOffset;
- TrackTarget(deltaTime, curState.ReferenceUp, offset, out Vector3 pos, out Quaternion orient);
- offset = orient * offset;
- // Respect minimum target distance on XZ plane
- var targetPosition = FollowTargetPosition;
- pos += GetOffsetForMinimumTargetDistance(
- pos, offset, curState.RawOrientation * Vector3.forward,
- curState.ReferenceUp, targetPosition);
-
- curState.RawPosition = pos + offset;
- curState.ReferenceUp = orient * Vector3.up;
- }
- }
- /// <summary>This is called to notify the us that a target got warped,
- /// so that we can update its internal state to make the camera
- /// also warp seamlessy.</summary>
- /// <param name="target">The object that was warped</param>
- /// <param name="positionDelta">The amount the target's position changed</param>
- public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
- {
- base.OnTargetObjectWarped(target, positionDelta);
- if (target == FollowTarget)
- m_PreviousTargetPosition += positionDelta;
- }
- /// <summary>
- /// Force the virtual camera to assume a given position and orientation
- /// </summary>
- /// <param name="pos">Worldspace pposition to take</param>
- /// <param name="rot">Worldspace orientation to take</param>
- public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
- {
- base.ForceCameraPosition(pos, rot);
- // Infer target pos from camera
- var targetRot = m_BindingMode == BindingMode.SimpleFollowWithWorldUp
- ? rot : GetReferenceOrientation(VirtualCamera.State.ReferenceUp);
- m_PreviousTargetPosition = pos - targetRot * EffectiveOffset;
- }
-
- /// <summary>Initializes the state for previous frame if appropriate.</summary>
- /// <param name="curState">The current camera state</param>
- /// <param name="deltaTime">Current effective deltaTime.</param>
- protected void InitPrevFrameStateInfo(
- ref CameraState curState, float deltaTime)
- {
- bool prevStateValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
- if (m_previousTarget != FollowTarget || !prevStateValid)
- {
- m_previousTarget = FollowTarget;
- m_targetOrientationOnAssign
- = (m_previousTarget == null) ? Quaternion.identity : FollowTargetRotation;
- }
- if (!prevStateValid)
- {
- m_PreviousTargetPosition = FollowTargetPosition;
- m_PreviousReferenceOrientation = GetReferenceOrientation(curState.ReferenceUp);
- }
- }
- /// <summary>Positions the virtual camera according to the transposer rules.</summary>
- /// <param name="deltaTime">Used for damping. If less than 0, no damping is done.</param>
- /// <param name="up">Current camera up</param>
- /// <param name="desiredCameraOffset">Where we want to put the camera relative to the follow target</param>
- /// <param name="outTargetPosition">Resulting camera position</param>
- /// <param name="outTargetOrient">Damped target orientation</param>
- protected void TrackTarget(
- float deltaTime, Vector3 up, Vector3 desiredCameraOffset,
- out Vector3 outTargetPosition, out Quaternion outTargetOrient)
- {
- var targetOrientation = GetReferenceOrientation(up);
- var dampedOrientation = targetOrientation;
- bool prevStateValid = deltaTime >= 0 && VirtualCamera.PreviousStateIsValid;
- if (prevStateValid)
- {
- if (m_AngularDampingMode == AngularDampingMode.Quaternion
- && m_BindingMode == BindingMode.LockToTarget)
- {
- float t = VirtualCamera.DetachedFollowTargetDamp(1, m_AngularDamping, deltaTime);
- dampedOrientation = Quaternion.Slerp(
- m_PreviousReferenceOrientation, targetOrientation, t);
- }
- else
- {
- var relative = (Quaternion.Inverse(m_PreviousReferenceOrientation)
- * targetOrientation).eulerAngles;
- for (int i = 0; i < 3; ++i)
- if (relative[i] > 180)
- relative[i] -= 360;
- relative = VirtualCamera.DetachedFollowTargetDamp(relative, AngularDamping, deltaTime);
- dampedOrientation = m_PreviousReferenceOrientation * Quaternion.Euler(relative);
- }
- }
- m_PreviousReferenceOrientation = dampedOrientation;
- var targetPosition = FollowTargetPosition;
- var currentPosition = m_PreviousTargetPosition;
- var previousOffset = prevStateValid ? m_PreviousOffset : desiredCameraOffset;
- var offsetDelta = desiredCameraOffset - previousOffset;
- if (offsetDelta.sqrMagnitude > 0.01f)
- {
- var q = UnityVectorExtensions.SafeFromToRotation(
- m_PreviousOffset.ProjectOntoPlane(up),
- desiredCameraOffset.ProjectOntoPlane(up), up);
- currentPosition = targetPosition + q * (m_PreviousTargetPosition - targetPosition);
- }
- m_PreviousOffset = desiredCameraOffset;
- // Adjust for damping, which is done in camera-offset-local coords
- var positionDelta = targetPosition - currentPosition;
- if (prevStateValid)
- {
- Quaternion dampingSpace;
- if (desiredCameraOffset.AlmostZero())
- dampingSpace = VcamState.RawOrientation;
- else
- dampingSpace = Quaternion.LookRotation(dampedOrientation * desiredCameraOffset, up);
- var localDelta = Quaternion.Inverse(dampingSpace) * positionDelta;
- localDelta = VirtualCamera.DetachedFollowTargetDamp(localDelta, Damping, deltaTime);
- positionDelta = dampingSpace * localDelta;
- }
- currentPosition += positionDelta;
- outTargetPosition = m_PreviousTargetPosition = currentPosition;
- outTargetOrient = dampedOrientation;
- }
- /// <summary>Return a new damped target position that respects the minimum
- /// distance from the real target</summary>
- /// <param name="dampedTargetPos">The effective position of the target, after damping</param>
- /// <param name="cameraOffset">Desired camera offset from target</param>
- /// <param name="cameraFwd">Current camera local +Z direction</param>
- /// <param name="up">Effective world up</param>
- /// <param name="actualTargetPos">The real undamped target position</param>
- /// <returns>New camera offset, potentially adjusted to respect minimum distance from target</returns>
- protected Vector3 GetOffsetForMinimumTargetDistance(
- Vector3 dampedTargetPos, Vector3 cameraOffset,
- Vector3 cameraFwd, Vector3 up, Vector3 actualTargetPos)
- {
- var posOffset = Vector3.zero;
- if (VirtualCamera.FollowTargetAttachment > 1 - Epsilon)
- {
- cameraOffset = cameraOffset.ProjectOntoPlane(up);
- var minDistance = cameraOffset.magnitude * 0.2f;
- if (minDistance > 0)
- {
- actualTargetPos = actualTargetPos.ProjectOntoPlane(up);
- dampedTargetPos = dampedTargetPos.ProjectOntoPlane(up);
- var cameraPos = dampedTargetPos + cameraOffset;
- var d = Vector3.Dot(
- actualTargetPos - cameraPos,
- (dampedTargetPos - cameraPos).normalized);
- if (d < minDistance)
- {
- var dir = actualTargetPos - dampedTargetPos;
- var len = dir.magnitude;
- if (len < 0.01f)
- dir = -cameraFwd.ProjectOntoPlane(up);
- else
- dir /= len;
- posOffset = dir * (minDistance - d);
- }
- m_PreviousTargetPosition += posOffset;
- }
- }
- return posOffset;
- }
- /// <summary>
- /// Damping speeds for each of the 3 axes of the offset from target
- /// </summary>
- protected Vector3 Damping
- {
- get
- {
- switch (m_BindingMode)
- {
- case BindingMode.SimpleFollowWithWorldUp:
- return new Vector3(0, m_YDamping, m_ZDamping);
- default:
- return new Vector3(m_XDamping, m_YDamping, m_ZDamping);
- }
- }
- }
- /// <summary>
- /// Damping speeds for each of the 3 axes of the target's rotation
- /// </summary>
- protected Vector3 AngularDamping
- {
- get
- {
- switch (m_BindingMode)
- {
- case BindingMode.LockToTargetNoRoll:
- return new Vector3(m_PitchDamping, m_YawDamping, 0);
- case BindingMode.LockToTargetWithWorldUp:
- return new Vector3(0, m_YawDamping, 0);
- case BindingMode.LockToTargetOnAssign:
- case BindingMode.WorldSpace:
- case BindingMode.SimpleFollowWithWorldUp:
- return Vector3.zero;
- default:
- return new Vector3(m_PitchDamping, m_YawDamping, m_RollDamping);
- }
- }
- }
- /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
- /// <param name="worldUp">Current effective world up</param>
- /// <returns>The position of the Follow target</returns>
- public virtual Vector3 GetTargetCameraPosition(Vector3 worldUp)
- {
- if (!IsValid)
- return Vector3.zero;
- return FollowTargetPosition + GetReferenceOrientation(worldUp) * EffectiveOffset;
- }
- /// <summary>State information for damping</summary>
- Vector3 m_PreviousTargetPosition = Vector3.zero;
- Quaternion m_PreviousReferenceOrientation = Quaternion.identity;
- Quaternion m_targetOrientationOnAssign = Quaternion.identity;
- Vector3 m_PreviousOffset;
- Transform m_previousTarget = null;
- /// <summary>Internal API for the Inspector Editor, so it can draw a marker at the target</summary>
- /// <param name="worldUp">Current effective world up</param>
- /// <returns>The rotation of the Follow target, as understood by the Transposer.
- /// This is not necessarily the same thing as the actual target rotation</returns>
- public Quaternion GetReferenceOrientation(Vector3 worldUp)
- {
- if (m_BindingMode == BindingMode.WorldSpace)
- return Quaternion.identity;
- if (FollowTarget != null)
- {
- Quaternion targetOrientation = FollowTarget.rotation;
- switch (m_BindingMode)
- {
- case BindingMode.LockToTargetOnAssign:
- return m_targetOrientationOnAssign;
- case BindingMode.LockToTargetWithWorldUp:
- {
- Vector3 fwd = (targetOrientation * Vector3.forward).ProjectOntoPlane(worldUp);
- if (fwd.AlmostZero())
- break;
- return Quaternion.LookRotation(fwd, worldUp);
- }
- case BindingMode.LockToTargetNoRoll:
- return Quaternion.LookRotation(targetOrientation * Vector3.forward, worldUp);
- case BindingMode.LockToTarget:
- return targetOrientation;
- case BindingMode.SimpleFollowWithWorldUp:
- {
- Vector3 fwd = (FollowTargetPosition - VcamState.RawPosition).ProjectOntoPlane(worldUp);
- if (fwd.AlmostZero())
- break;
- return Quaternion.LookRotation(fwd, worldUp);
- }
- }
- }
- // Gimbal lock situation - use previous orientation if it exists
- #if UNITY_2019_1_OR_NEWER
- return m_PreviousReferenceOrientation.normalized;
- #else
- return m_PreviousReferenceOrientation.Normalized();
- #endif
- }
- }
- }
|