Rasagar/Library/PackageCache/com.unity.cinemachine/Runtime/Components/CinemachineComposer.cs
2024-08-26 23:07:20 +03:00

514 lines
26 KiB
C#

using UnityEngine;
using System;
using Cinemachine.Utility;
using UnityEngine.Serialization;
namespace Cinemachine
{
/// <summary>
/// This is a CinemachineComponent in the Aim section of the component pipeline.
/// Its job is to aim the camera at the vcam's LookAt target object, with
/// configurable offsets, damping, and composition rules.
///
/// The composer does not change the camera's position. It will only pan and tilt the
/// camera where it is, in order to get the desired framing. To move the camera, you have
/// to use the virtual camera's Body section.
/// </summary>
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
[AddComponentMenu("")] // Don't display in add component menu
[SaveDuringPlay]
public class CinemachineComposer : CinemachineComponentBase
{
/// <summary>Target offset from the object's center in LOCAL space which
/// the Composer tracks. Use this to fine-tune the tracking target position
/// when the desired area is not in the tracked object's center</summary>
[Tooltip("Target offset from the target object's center in target-local space. Use this to "
+ "fine-tune the tracking target position when the desired area is not the tracked object's center.")]
public Vector3 m_TrackedObjectOffset = Vector3.zero;
/// <summary>This setting will instruct the composer to adjust its target offset based
/// on the motion of the target. The composer will look at a point where it estimates
/// the target will be this many seconds into the future. Note that this setting is sensitive
/// to noisy animation, and can amplify the noise, resulting in undesirable camera jitter.
/// If the camera jitters unacceptably when the target is in motion, turn down this setting,
/// or animate the target more smoothly.</summary>
[Space]
[Tooltip("This setting will instruct the composer to adjust its target offset based on the motion "
+ "of the target. The composer will look at a point where it estimates the target will be this "
+ "many seconds into the future. Note that this setting is sensitive to noisy animation, and "
+ "can amplify the noise, resulting in undesirable camera jitter. If the camera jitters "
+ "unacceptably when the target is in motion, turn down this setting, or animate the target more smoothly.")]
[Range(0f, 1f)]
public float m_LookaheadTime = 0;
/// <summary>Controls the smoothness of the lookahead algorithm. Larger values smooth out
/// jittery predictions and also increase prediction lag</summary>
[Tooltip("Controls the smoothness of the lookahead algorithm. Larger values smooth "
+ "out jittery predictions and also increase prediction lag")]
[Range(0, 30)]
public float m_LookaheadSmoothing = 0;
/// <summary>If checked, movement along the Y axis will be ignored for lookahead calculations</summary>
[Tooltip("If checked, movement along the Y axis will be ignored for lookahead calculations")]
public bool m_LookaheadIgnoreY;
/// <summary>How aggressively the camera tries to follow the target in the screen-horizontal direction.
/// Small numbers are more responsive, rapidly orienting the camera to keep the target in
/// the dead zone. Larger numbers give a more heavy slowly responding camera.
/// Using different vertical and horizontal settings can yield a wide range of camera behaviors.</summary>
[Space]
[Range(0f, 20)]
[Tooltip("How aggressively the camera tries to follow the target in the screen-horizontal direction. "
+ "Small numbers are more responsive, rapidly orienting the camera to keep the target in "
+ "the dead zone. Larger numbers give a more heavy slowly responding camera. Using different "
+ "vertical and horizontal settings can yield a wide range of camera behaviors.")]
public float m_HorizontalDamping = 0.5f;
/// <summary>How aggressively the camera tries to follow the target in the screen-vertical direction.
/// Small numbers are more responsive, rapidly orienting the camera to keep the target in
/// the dead zone. Larger numbers give a more heavy slowly responding camera. Using different vertical
/// and horizontal settings can yield a wide range of camera behaviors.</summary>
[Range(0f, 20)]
[Tooltip("How aggressively the camera tries to follow the target in the screen-vertical direction. "
+ "Small numbers are more responsive, rapidly orienting the camera to keep the target in "
+ "the dead zone. Larger numbers give a more heavy slowly responding camera. Using different "
+ "vertical and horizontal settings can yield a wide range of camera behaviors.")]
public float m_VerticalDamping = 0.5f;
/// <summary>Horizontal screen position for target. The camera will rotate to the position the tracked object here</summary>
[Space]
[Range(-0.5f, 1.5f)]
[Tooltip("Horizontal screen position for target. The camera will rotate to position the tracked object here.")]
public float m_ScreenX = 0.5f;
/// <summary>Vertical screen position for target, The camera will rotate to to position the tracked object here</summary>
[Range(-0.5f, 1.5f)]
[Tooltip("Vertical screen position for target, The camera will rotate to position the tracked object here.")]
public float m_ScreenY = 0.5f;
/// <summary>Camera will not rotate horizontally if the target is within this range of the position</summary>
[Range(0f, 2f)]
[Tooltip("Camera will not rotate horizontally if the target is within this range of the position.")]
public float m_DeadZoneWidth = 0f;
/// <summary>Camera will not rotate vertically if the target is within this range of the position</summary>
[Range(0f, 2f)]
[Tooltip("Camera will not rotate vertically if the target is within this range of the position.")]
public float m_DeadZoneHeight = 0f;
/// <summary>When target is within this region, camera will gradually move to re-align
/// towards the desired position, depending onm the damping speed</summary>
[Range(0f, 2f)]
[Tooltip("When target is within this region, camera will gradually rotate horizontally to re-align "
+ "towards the desired position, depending on the damping speed.")]
public float m_SoftZoneWidth = 0.8f;
/// <summary>When target is within this region, camera will gradually move to re-align
/// towards the desired position, depending onm the damping speed</summary>
[Range(0f, 2f)]
[Tooltip("When target is within this region, camera will gradually rotate vertically to re-align "
+ "towards the desired position, depending on the damping speed.")]
public float m_SoftZoneHeight = 0.8f;
/// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
[Range(-0.5f, 0.5f)]
[Tooltip("A non-zero bias will move the target position horizontally away from the center of the soft zone.")]
public float m_BiasX = 0f;
/// <summary>A non-zero bias will move the targt position away from the center of the soft zone</summary>
[Range(-0.5f, 0.5f)]
[Tooltip("A non-zero bias will move the target position vertically away from the center of the soft zone.")]
public float m_BiasY = 0f;
/// <summary>Force target to center of screen when this camera activates.
/// If false, will clamp target to the edges of the dead zone</summary>
[Tooltip("Force target to center of screen when this camera activates. If false, will "
+ "clamp target to the edges of the dead zone")]
public bool m_CenterOnActivate = true;
/// <summary>True if component is enabled and has a LookAt defined</summary>
public override bool IsValid { get { return enabled && LookAtTarget != null; } }
/// <summary>Get the Cinemachine Pipeline stage that this component implements.
/// Always returns the Aim stage</summary>
public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Aim; } }
/// <summary>Internal API for inspector</summary>
public Vector3 TrackedPoint { get; private set; }
/// <summary>Apply the target offsets to the target location.
/// Also set the TrackedPoint property, taking lookahead into account.</summary>
/// <param name="lookAt">The unoffset LookAt point</param>
/// <param name="up">Currest effective world up</param>
/// <param name="deltaTime">Current effective deltaTime</param>
/// <returns>The LookAt point with the offset applied</returns>
protected virtual Vector3 GetLookAtPointAndSetTrackedPoint(
Vector3 lookAt, Vector3 up, float deltaTime)
{
Vector3 pos = lookAt;
if (LookAtTarget != null)
pos += LookAtTargetRotation * m_TrackedObjectOffset;
if (m_LookaheadTime < Epsilon)
TrackedPoint = pos;
else
{
var resetLookahead = VirtualCamera.LookAtTargetChanged || !VirtualCamera.PreviousStateIsValid;
m_Predictor.Smoothing = m_LookaheadSmoothing;
m_Predictor.AddPosition(pos, resetLookahead ? -1 : deltaTime, m_LookaheadTime);
var delta = m_Predictor.PredictPositionDelta(m_LookaheadTime);
if (m_LookaheadIgnoreY)
delta = delta.ProjectOntoPlane(up);
TrackedPoint = pos + delta;
}
return pos;
}
/// <summary>State information for damping</summary>
Vector3 m_CameraPosPrevFrame = Vector3.zero;
Vector3 m_LookAtPrevFrame = Vector3.zero;
Vector2 m_ScreenOffsetPrevFrame = Vector2.zero;
Quaternion m_CameraOrientationPrevFrame = Quaternion.identity;
internal PositionPredictor m_Predictor = new PositionPredictor();
/// <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 == LookAtTarget)
{
m_CameraPosPrevFrame += positionDelta;
m_LookAtPrevFrame += positionDelta;
m_Predictor.ApplyTransformDelta(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);
m_CameraPosPrevFrame = pos;
m_CameraOrientationPrevFrame = rot;
}
/// <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(m_HorizontalDamping, m_VerticalDamping);
}
/// <summary>Sets the state's ReferenceLookAt, applying the offset.</summary>
/// <param name="curState">Input state that must be mutated</param>
/// <param name="deltaTime">Current effective deltaTime</param>
public override void PrePipelineMutateCameraState(ref CameraState curState, float deltaTime)
{
if (IsValid && curState.HasLookAt)
curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(
curState.ReferenceLookAt, curState.ReferenceUp, deltaTime);
}
/// <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)
{
if (!IsValid || !curState.HasLookAt)
return;
// Correct the tracked point in the event that it's behind the camera
// while the real target is in front
if (!(TrackedPoint - curState.ReferenceLookAt).AlmostZero())
{
Vector3 mid = Vector3.Lerp(curState.CorrectedPosition, curState.ReferenceLookAt, 0.5f);
Vector3 toLookAt = curState.ReferenceLookAt - mid;
Vector3 toTracked = TrackedPoint - mid;
if (Vector3.Dot(toLookAt, toTracked) < 0)
{
float t = Vector3.Distance(curState.ReferenceLookAt, mid)
/ Vector3.Distance(curState.ReferenceLookAt, TrackedPoint);
TrackedPoint = Vector3.Lerp(curState.ReferenceLookAt, TrackedPoint, t);
}
}
float targetDistance = (TrackedPoint - curState.CorrectedPosition).magnitude;
if (targetDistance < Epsilon)
{
if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
curState.RawOrientation = m_CameraOrientationPrevFrame;
return; // navel-gazing, get outa here
}
// Expensive FOV calculations
mCache.UpdateCache(curState.Lens, SoftGuideRect, HardGuideRect, targetDistance);
Quaternion rigOrientation = curState.RawOrientation;
if (deltaTime < 0 || !VirtualCamera.PreviousStateIsValid)
{
// No damping, just snap to central bounds, skipping the soft zone
rigOrientation = Quaternion.LookRotation(
rigOrientation * Vector3.forward, curState.ReferenceUp);
Rect rect = mCache.mFovSoftGuideRect;
if (m_CenterOnActivate)
rect = new Rect(rect.center, Vector2.zero); // Force to center
RotateToScreenBounds(
ref curState, rect, curState.ReferenceLookAt,
ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
}
else
{
// Start with previous frame's orientation (but with current up)
Vector3 dir = m_LookAtPrevFrame - m_CameraPosPrevFrame;
if (dir.AlmostZero())
rigOrientation = Quaternion.LookRotation(
m_CameraOrientationPrevFrame * Vector3.forward, curState.ReferenceUp);
else
{
dir = Quaternion.Euler(curState.PositionDampingBypass) * dir;
rigOrientation = Quaternion.LookRotation(dir, curState.ReferenceUp);
rigOrientation = rigOrientation.ApplyCameraRotation(
-m_ScreenOffsetPrevFrame, curState.ReferenceUp);
}
// Move target through the soft zone, with damping
RotateToScreenBounds(
ref curState, mCache.mFovSoftGuideRect, TrackedPoint,
ref rigOrientation, mCache.mFov, mCache.mFovH, deltaTime);
// Force the actual target (not the lookahead one) into the hard bounds, no damping
if (deltaTime < 0 || VirtualCamera.LookAtTargetAttachment > 1 - Epsilon)
RotateToScreenBounds(
ref curState, mCache.mFovHardGuideRect, curState.ReferenceLookAt,
ref rigOrientation, mCache.mFov, mCache.mFovH, -1);
}
m_CameraPosPrevFrame = curState.CorrectedPosition;
m_LookAtPrevFrame = TrackedPoint;
m_CameraOrientationPrevFrame = UnityQuaternionExtensions.Normalized(rigOrientation);
m_ScreenOffsetPrevFrame = m_CameraOrientationPrevFrame.GetCameraRotationToTarget(
m_LookAtPrevFrame - curState.CorrectedPosition, curState.ReferenceUp);
curState.RawOrientation = m_CameraOrientationPrevFrame;
}
/// <summary>Internal API for the inspector editor</summary>
internal Rect SoftGuideRect
{
get
{
return new Rect(
m_ScreenX - m_DeadZoneWidth / 2, m_ScreenY - m_DeadZoneHeight / 2,
m_DeadZoneWidth, m_DeadZoneHeight);
}
set
{
m_DeadZoneWidth = Mathf.Clamp(value.width, 0, 2);
m_DeadZoneHeight = Mathf.Clamp(value.height, 0, 2);
m_ScreenX = Mathf.Clamp(value.x + m_DeadZoneWidth / 2, -0.5f, 1.5f);
m_ScreenY = Mathf.Clamp(value.y + m_DeadZoneHeight / 2, -0.5f, 1.5f);
m_SoftZoneWidth = Mathf.Max(m_SoftZoneWidth, m_DeadZoneWidth);
m_SoftZoneHeight = Mathf.Max(m_SoftZoneHeight, m_DeadZoneHeight);
}
}
/// <summary>Internal API for the inspector editor</summary>
internal Rect HardGuideRect
{
get
{
Rect r = new Rect(
m_ScreenX - m_SoftZoneWidth / 2, m_ScreenY - m_SoftZoneHeight / 2,
m_SoftZoneWidth, m_SoftZoneHeight);
r.position += new Vector2(
m_BiasX * (m_SoftZoneWidth - m_DeadZoneWidth),
m_BiasY * (m_SoftZoneHeight - m_DeadZoneHeight));
return r;
}
set
{
m_SoftZoneWidth = Mathf.Clamp(value.width, 0, 2f);
m_SoftZoneHeight = Mathf.Clamp(value.height, 0, 2f);
m_DeadZoneWidth = Mathf.Min(m_DeadZoneWidth, m_SoftZoneWidth);
m_DeadZoneHeight = Mathf.Min(m_DeadZoneHeight, m_SoftZoneHeight);
}
}
// Cache for some expensive calculations
struct FovCache
{
public Rect mFovSoftGuideRect;
public Rect mFovHardGuideRect;
public float mFovH;
public float mFov;
float mOrthoSizeOverDistance;
float mAspect;
Rect mSoftGuideRect;
Rect mHardGuideRect;
public void UpdateCache(
LensSettings lens, Rect softGuide, Rect hardGuide, float targetDistance)
{
bool recalculate = mAspect != lens.Aspect
|| softGuide != mSoftGuideRect || hardGuide != mHardGuideRect;
if (lens.Orthographic)
{
float orthoOverDistance = Mathf.Abs(lens.OrthographicSize / targetDistance);
if (mOrthoSizeOverDistance == 0
|| Mathf.Abs(orthoOverDistance - mOrthoSizeOverDistance) / mOrthoSizeOverDistance
> mOrthoSizeOverDistance * 0.01f)
recalculate = true;
if (recalculate)
{
// Calculate effective fov - fake it for ortho based on target distance
mFov = Mathf.Rad2Deg * 2 * Mathf.Atan(orthoOverDistance);
mFovH = Mathf.Rad2Deg * 2 * Mathf.Atan(lens.Aspect * orthoOverDistance);
mOrthoSizeOverDistance = orthoOverDistance;
}
}
else
{
var verticalFOV = lens.FieldOfView;
if (mFov != verticalFOV)
recalculate = true;
if (recalculate)
{
mFov = verticalFOV;
double radHFOV = 2 * Math.Atan(Math.Tan(mFov * Mathf.Deg2Rad / 2) * lens.Aspect);
mFovH = (float)(Mathf.Rad2Deg * radHFOV);
mOrthoSizeOverDistance = 0;
}
}
if (recalculate)
{
mFovSoftGuideRect = ScreenToFOV(softGuide, mFov, mFovH, lens.Aspect);
mSoftGuideRect = softGuide;
mFovHardGuideRect = ScreenToFOV(hardGuide, mFov, mFovH, lens.Aspect);
mHardGuideRect = hardGuide;
mAspect = lens.Aspect;
}
}
// Convert from screen coords to normalized FOV angular coords
private Rect ScreenToFOV(Rect rScreen, float fov, float fovH, float aspect)
{
Rect r = new Rect(rScreen);
Matrix4x4 persp = Matrix4x4.Perspective(fov, aspect, 0.0001f, 2f).inverse;
Vector3 p = persp.MultiplyPoint(new Vector3(0, (r.yMin * 2f) - 1f, 0.5f)); p.z = -p.z;
float angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
r.yMin = ((fov / 2) + angle) / fov;
p = persp.MultiplyPoint(new Vector3(0, (r.yMax * 2f) - 1f, 0.5f)); p.z = -p.z;
angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.left);
r.yMax = ((fov / 2) + angle) / fov;
p = persp.MultiplyPoint(new Vector3((r.xMin * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
r.xMin = ((fovH / 2) + angle) / fovH;
p = persp.MultiplyPoint(new Vector3((r.xMax * 2f) - 1f, 0, 0.5f)); p.z = -p.z;
angle = UnityVectorExtensions.SignedAngle(Vector3.forward, p, Vector3.up);
r.xMax = ((fovH / 2) + angle) / fovH;
return r;
}
}
FovCache mCache;
/// <summary>
/// Adjust the rigOrientation to put the camera within the screen bounds.
/// If deltaTime >= 0 then damping will be applied.
/// Assumes that currentOrientation fwd is such that input rigOrientation's
/// local up is NEVER NEVER NEVER pointing downwards, relative to
/// state.ReferenceUp. If this condition is violated
/// then you will see crazy spinning. That's the symptom.
/// </summary>
private void RotateToScreenBounds(
ref CameraState state, Rect screenRect, Vector3 trackedPoint,
ref Quaternion rigOrientation, float fov, float fovH, float deltaTime)
{
Vector3 targetDir = trackedPoint - state.CorrectedPosition;
Vector2 rotToRect = rigOrientation.GetCameraRotationToTarget(targetDir, state.ReferenceUp);
// Bring it to the edge of screenRect, if outside. Leave it alone if inside.
ClampVerticalBounds(ref screenRect, targetDir, state.ReferenceUp, fov);
float min = (screenRect.yMin - 0.5f) * fov;
float max = (screenRect.yMax - 0.5f) * fov;
if (rotToRect.x < min)
rotToRect.x -= min;
else if (rotToRect.x > max)
rotToRect.x -= max;
else
rotToRect.x = 0;
min = (screenRect.xMin - 0.5f) * fovH;
max = (screenRect.xMax - 0.5f) * fovH;
if (rotToRect.y < min)
rotToRect.y -= min;
else if (rotToRect.y > max)
rotToRect.y -= max;
else
rotToRect.y = 0;
// Apply damping
if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
{
rotToRect.x = VirtualCamera.DetachedLookAtTargetDamp(
rotToRect.x, m_VerticalDamping, deltaTime);
rotToRect.y = VirtualCamera.DetachedLookAtTargetDamp(
rotToRect.y, m_HorizontalDamping, deltaTime);
}
// Rotate
rigOrientation = rigOrientation.ApplyCameraRotation(rotToRect, state.ReferenceUp);
}
/// <summary>
/// Prevent upside-down camera situation. This can happen if we have a high
/// camera pitch combined with composer settings that cause the camera to tilt
/// beyond the vertical in order to produce the desired framing. We prevent this by
/// clamping the composer's vertical settings so that this situation can't happen.
/// </summary>
private bool ClampVerticalBounds(ref Rect r, Vector3 dir, Vector3 up, float fov)
{
float angle = UnityVectorExtensions.Angle(dir, up);
float halfFov = (fov / 2f) + 1; // give it a little extra to accommodate precision errors
if (angle < halfFov)
{
// looking up
float maxY = 1f - (halfFov - angle) / fov;
if (r.yMax > maxY)
{
r.yMin = Mathf.Min(r.yMin, maxY);
r.yMax = Mathf.Min(r.yMax, maxY);
return true;
}
}
if (angle > (180 - halfFov))
{
// looking down
float minY = (angle - (180 - halfFov)) / fov;
if (minY > r.yMin)
{
r.yMin = Mathf.Max(r.yMin, minY);
r.yMax = Mathf.Max(r.yMax, minY);
return true;
}
}
return false;
}
}
}