using UnityEngine;
using Cinemachine.Utility;
using System.Collections.Generic;
namespace Cinemachine
{
///
/// The output of the Cinemachine engine for a specific virtual camera. The information
/// in this struct can be blended, and provides what is needed to calculate an
/// appropriate camera position, orientation, and lens setting.
///
/// Raw values are what the Cinemachine behaviours generate. The correction channel
/// holds perturbations to the raw values - e.g. noise or smoothing, or obstacle
/// avoidance corrections. Coirrections are not considered when making time-based
/// calculations such as damping.
///
/// The Final position and orientation is the comination of the raw values and
/// their corrections.
///
public struct CameraState
{
///
/// Camera Lens Settings.
///
public LensSettings Lens;
///
/// Which way is up. World space unit vector. Must have a length of 1.
///
public Vector3 ReferenceUp;
///
/// The world space focus point of the camera. What the camera wants to look at.
/// There is a special constant define to represent "nothing". Be careful to
/// check for that (or check the HasLookAt property).
///
public Vector3 ReferenceLookAt;
///
/// Returns true if this state has a valid ReferenceLookAt value.
///
#pragma warning disable 1718 // comparison made to same variable
public bool HasLookAt => ReferenceLookAt == ReferenceLookAt; // will be false if NaN
///
/// This constant represents "no point in space" or "no direction".
///
public static Vector3 kNoPoint = new Vector3(float.NaN, float.NaN, float.NaN);
///
/// Raw (un-corrected) world space position of this camera
///
public Vector3 RawPosition;
///
/// Raw (un-corrected) world space orientation of this camera
///
public Quaternion RawOrientation;
/// This is a way for the Body component to bypass aim damping,
/// useful for when the body needs to rotate its point of view, but does not
/// want interference from the aim damping. The value is the camera
/// rotation, in Euler degrees.
public Vector3 PositionDampingBypass;
///
/// Subjective estimation of how "good" the shot is.
/// Larger values mean better quality. Default is 1.
///
public float ShotQuality;
///
/// Position correction. This will be added to the raw position.
/// This value doesn't get fed back into the system when calculating the next frame.
/// Can be noise, or smoothing, or both, or something else.
///
public Vector3 PositionCorrection;
///
/// Orientation correction. This will be added to the raw orientation.
/// This value doesn't get fed back into the system when calculating the next frame.
/// Can be noise, or smoothing, or both, or something else.
///
public Quaternion OrientationCorrection;
///
/// Position with correction applied.
///
public Vector3 CorrectedPosition => RawPosition + PositionCorrection;
///
/// Orientation with correction applied.
///
public Quaternion CorrectedOrientation => RawOrientation * OrientationCorrection;
///
/// Position with correction applied. This is what the final camera gets.
///
public Vector3 FinalPosition => RawPosition + PositionCorrection;
///
/// Orientation with correction and dutch applied. This is what the final camera gets.
///
public Quaternion FinalOrientation
{
get
{
if (Mathf.Abs(Lens.Dutch) > UnityVectorExtensions.Epsilon)
return CorrectedOrientation * Quaternion.AngleAxis(Lens.Dutch, Vector3.forward);
return CorrectedOrientation;
}
}
///
/// Combine these hints to influence how blending is done, and how state is applied to the camera.
///
public enum BlendHintValue
{
/// Normal state blending
Nothing = 0,
/// This state does not affect the camera position
NoPosition = 1,
/// This state does not affect the camera rotation
NoOrientation = 2,
/// Combination of NoPosition and NoOrientation
NoTransform = NoPosition | NoOrientation,
/// Spherical blend about the LookAt target (if any)
SphericalPositionBlend = 4,
/// Cylindrical blend about the LookAt target (if any)
CylindricalPositionBlend = 8,
/// Radial blend when the LookAt target changes(if any)
RadialAimBlend = 16,
/// Ignore the LookAt target and just slerp the orientation
IgnoreLookAtTarget = 32,
/// This state does not affect the lens
NoLens = 64,
}
///
/// Combine these hints to influence how blending is done, and how state is applied to the camera.
///
public BlendHintValue BlendHint;
///
/// State with default values
///
public static CameraState Default
{
get
{
CameraState state = new CameraState();
state.Lens = LensSettings.Default;
state.ReferenceUp = Vector3.up;
state.ReferenceLookAt = kNoPoint;
state.RawPosition = Vector3.zero;
state.RawOrientation = Quaternion.identity;
state.ShotQuality = 1;
state.PositionCorrection = Vector3.zero;
state.OrientationCorrection = Quaternion.identity;
state.PositionDampingBypass = Vector3.zero;
state.BlendHint = BlendHintValue.Nothing;
return state;
}
}
/// Opaque structure represent extra blendable stuff and its weight.
/// The base system ignores this data - it is intended for extension modules
public struct CustomBlendable
{
/// The custom stuff that the extension module will consider
public Object m_Custom;
/// The weight of the custom stuff. Must be 0...1
public float m_Weight;
/// Constructor with specific values
/// The custom stuff that the extension module will consider
/// The weight of the custom stuff. Must be 0...1
public CustomBlendable(Object custom, float weight)
{ m_Custom = custom; m_Weight = weight; }
};
// This is to avoid excessive GC allocs
CustomBlendable mCustom0;
CustomBlendable mCustom1;
CustomBlendable mCustom2;
CustomBlendable mCustom3;
List m_CustomOverflow;
/// The number of custom blendables that will be applied to the camera.
/// The base system manages but otherwise ignores this data - it is intended for
/// extension modules
public int NumCustomBlendables { get; private set; }
/// Get a custom blendable that will be applied to the camera.
/// The base system manages but otherwise ignores this data - it is intended for
/// extension modules
/// Which one to get. Must be in range [0...NumCustomBlendables)
/// The custom blendable at the specified index.
public CustomBlendable GetCustomBlendable(int index)
{
switch (index)
{
case 0: return mCustom0;
case 1: return mCustom1;
case 2: return mCustom2;
case 3: return mCustom3;
default:
{
index -= 4;
if (m_CustomOverflow != null && index < m_CustomOverflow.Count)
return m_CustomOverflow[index];
return new CustomBlendable(null, 0);
}
}
}
int FindCustomBlendable(Object custom)
{
if (mCustom0.m_Custom == custom)
return 0;
if (mCustom1.m_Custom == custom)
return 1;
if (mCustom2.m_Custom == custom)
return 2;
if (mCustom3.m_Custom == custom)
return 3;
if (m_CustomOverflow != null)
{
for (int i = 0; i < m_CustomOverflow.Count; ++i)
if (m_CustomOverflow[i].m_Custom == custom)
return i + 4;
}
return -1;
}
/// Add a custom blendable to the pot for eventual application to the camera.
/// The base system manages but otherwise ignores this data - it is intended for
/// extension modules
/// The custom blendable to add. If b.m_Custom is the same as an
/// already-added custom blendable, then they will be merged and the weights combined.
public void AddCustomBlendable(CustomBlendable b)
{
// Attempt to merge common blendables to avoid growth
int index = FindCustomBlendable(b.m_Custom);
if (index >= 0)
b.m_Weight += GetCustomBlendable(index).m_Weight;
else
index = NumCustomBlendables++;
switch (index)
{
case 0: mCustom0 = b; break;
case 1: mCustom1 = b; break;
case 2: mCustom2 = b; break;
case 3: mCustom3 = b; break;
default:
{
index -= 4;
if (m_CustomOverflow == null)
m_CustomOverflow = new List();
if (index < m_CustomOverflow.Count)
m_CustomOverflow[index] = b;
else
m_CustomOverflow.Add(b);
break;
}
}
}
/// Intelligently blend the contents of two states.
/// The first state, corresponding to t=0
/// The second state, corresponding to t=1
/// How much to interpolate. Internally clamped to 0..1
/// Linearly interpolated CameraState
public static CameraState Lerp(CameraState stateA, CameraState stateB, float t)
{
t = Mathf.Clamp01(t);
float adjustedT = t;
CameraState state = new CameraState();
// Combine the blend hints intelligently
if (((stateA.BlendHint & stateB.BlendHint) & BlendHintValue.NoPosition) != 0)
state.BlendHint |= BlendHintValue.NoPosition;
if (((stateA.BlendHint & stateB.BlendHint) & BlendHintValue.NoOrientation) != 0)
state.BlendHint |= BlendHintValue.NoOrientation;
if (((stateA.BlendHint & stateB.BlendHint) & BlendHintValue.NoLens) != 0)
state.BlendHint |= BlendHintValue.NoLens;
if (((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.SphericalPositionBlend) != 0)
state.BlendHint |= BlendHintValue.SphericalPositionBlend;
if (((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.CylindricalPositionBlend) != 0)
state.BlendHint |= BlendHintValue.CylindricalPositionBlend;
if (((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.NoLens) == 0)
state.Lens = LensSettings.Lerp(stateA.Lens, stateB.Lens, t);
else if (((stateA.BlendHint & stateB.BlendHint) & BlendHintValue.NoLens) == 0)
{
if ((stateA.BlendHint & BlendHintValue.NoLens) != 0)
state.Lens = stateB.Lens;
else
state.Lens = stateA.Lens;
}
state.ReferenceUp = Vector3.Slerp(stateA.ReferenceUp, stateB.ReferenceUp, t);
state.ShotQuality = Mathf.Lerp(stateA.ShotQuality, stateB.ShotQuality, t);
state.PositionCorrection = ApplyPosBlendHint(
stateA.PositionCorrection, stateA.BlendHint,
stateB.PositionCorrection, stateB.BlendHint,
state.PositionCorrection,
Vector3.Lerp(stateA.PositionCorrection, stateB.PositionCorrection, t));
state.OrientationCorrection = ApplyRotBlendHint(
stateA.OrientationCorrection, stateA.BlendHint,
stateB.OrientationCorrection, stateB.BlendHint,
state.OrientationCorrection,
Quaternion.Slerp(stateA.OrientationCorrection, stateB.OrientationCorrection, t));
// LookAt target
if (!stateA.HasLookAt || !stateB.HasLookAt)
state.ReferenceLookAt = kNoPoint;
else
{
// Re-interpolate FOV to preserve target composition, if possible
float fovA = stateA.Lens.FieldOfView;
float fovB = stateB.Lens.FieldOfView;
if (((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.NoLens) == 0
&& !state.Lens.Orthographic && !Mathf.Approximately(fovA, fovB))
{
LensSettings lens = state.Lens;
lens.FieldOfView = InterpolateFOV(
fovA, fovB,
Mathf.Max((stateA.ReferenceLookAt - stateA.CorrectedPosition).magnitude, stateA.Lens.NearClipPlane),
Mathf.Max((stateB.ReferenceLookAt - stateB.CorrectedPosition).magnitude, stateB.Lens.NearClipPlane), t);
state.Lens = lens;
// Make sure we preserve the screen composition through FOV changes
adjustedT = Mathf.Abs((lens.FieldOfView - fovA) / (fovB - fovA));
}
// Linear interpolation of lookAt target point
state.ReferenceLookAt = Vector3.Lerp(
stateA.ReferenceLookAt, stateB.ReferenceLookAt, adjustedT);
}
// Raw position
state.RawPosition = ApplyPosBlendHint(
stateA.RawPosition, stateA.BlendHint,
stateB.RawPosition, stateB.BlendHint,
state.RawPosition, state.InterpolatePosition(
stateA.RawPosition, stateA.ReferenceLookAt,
stateB.RawPosition, stateB.ReferenceLookAt,
t));
// Interpolate the LookAt in Screen Space if requested
if (state.HasLookAt
&& ((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.RadialAimBlend) != 0)
{
state.ReferenceLookAt = state.RawPosition + Vector3.Slerp(
stateA.ReferenceLookAt - state.RawPosition,
stateB.ReferenceLookAt - state.RawPosition, adjustedT);
}
// Clever orientation interpolation
Quaternion newOrient = state.RawOrientation;
if (((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.NoOrientation) == 0)
{
Vector3 dirTarget = Vector3.zero;
if (state.HasLookAt)//&& ((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.RadialAimBlend) == 0)
{
// If orientations are different, use LookAt to blend them
float angle = Quaternion.Angle(stateA.RawOrientation, stateB.RawOrientation);
if (angle > UnityVectorExtensions.Epsilon)
dirTarget = state.ReferenceLookAt - state.CorrectedPosition;
}
if (dirTarget.AlmostZero()
|| ((stateA.BlendHint | stateB.BlendHint) & BlendHintValue.IgnoreLookAtTarget) != 0)
{
// Don't know what we're looking at - can only slerp
newOrient = Quaternion.Slerp(stateA.RawOrientation, stateB.RawOrientation, t);
}
else
{
// Rotate while preserving our lookAt target
var up = state.ReferenceUp;
dirTarget.Normalize();
if (Vector3.Cross(dirTarget, up).AlmostZero())
{
// Looking up or down at the pole
newOrient = Quaternion.Slerp(stateA.RawOrientation, stateB.RawOrientation, t);
up = newOrient * Vector3.up;
}
// Blend the desired offsets from center
newOrient = Quaternion.LookRotation(dirTarget, up);
var deltaA = -stateA.RawOrientation.GetCameraRotationToTarget(
stateA.ReferenceLookAt - stateA.CorrectedPosition, up);
var deltaB = -stateB.RawOrientation.GetCameraRotationToTarget(
stateB.ReferenceLookAt - stateB.CorrectedPosition, up);
newOrient = newOrient.ApplyCameraRotation(Vector2.Lerp(deltaA, deltaB, adjustedT), up);
}
}
state.RawOrientation = ApplyRotBlendHint(
stateA.RawOrientation, stateA.BlendHint,
stateB.RawOrientation, stateB.BlendHint,
state.RawOrientation, newOrient);
// Accumulate the custom blendables and apply the weights
for (int i = 0; i < stateA.NumCustomBlendables; ++i)
{
CustomBlendable b = stateA.GetCustomBlendable(i);
b.m_Weight *= (1-t);
if (b.m_Weight > 0)
state.AddCustomBlendable(b);
}
for (int i = 0; i < stateB.NumCustomBlendables; ++i)
{
CustomBlendable b = stateB.GetCustomBlendable(i);
b.m_Weight *= t;
if (b.m_Weight > 0)
state.AddCustomBlendable(b);
}
return state;
}
static float InterpolateFOV(float fovA, float fovB, float dA, float dB, float t)
{
// We interpolate shot height
float hA = dA * 2f * Mathf.Tan(fovA * Mathf.Deg2Rad / 2f);
float hB = dB * 2f * Mathf.Tan(fovB * Mathf.Deg2Rad / 2f);
float h = Mathf.Lerp(hA, hB, t);
float fov = 179f;
float d = Mathf.Lerp(dA, dB, t);
if (d > UnityVectorExtensions.Epsilon)
fov = 2f * Mathf.Atan(h / (2 * d)) * Mathf.Rad2Deg;
return Mathf.Clamp(fov, Mathf.Min(fovA, fovB), Mathf.Max(fovA, fovB));
}
static Vector3 ApplyPosBlendHint(
Vector3 posA, BlendHintValue hintA,
Vector3 posB, BlendHintValue hintB,
Vector3 original, Vector3 blended)
{
if (((hintA | hintB) & BlendHintValue.NoPosition) == 0)
return blended;
if (((hintA & hintB) & BlendHintValue.NoPosition) != 0)
return original;
if ((hintA & BlendHintValue.NoPosition) != 0)
return posB;
return posA;
}
static Quaternion ApplyRotBlendHint(
Quaternion rotA, BlendHintValue hintA,
Quaternion rotB, BlendHintValue hintB,
Quaternion original, Quaternion blended)
{
if (((hintA | hintB) & BlendHintValue.NoOrientation) == 0)
return blended;
if (((hintA & hintB) & BlendHintValue.NoOrientation) != 0)
return original;
if ((hintA & BlendHintValue.NoOrientation) != 0)
return rotB;
return rotA;
}
Vector3 InterpolatePosition(
Vector3 posA, Vector3 pivotA,
Vector3 posB, Vector3 pivotB,
float t)
{
if (pivotA == pivotA && pivotB == pivotB) // check for NaN
{
if ((BlendHint & BlendHintValue.CylindricalPositionBlend) != 0)
{
// Cylindrical interpolation about pivot
var a = Vector3.ProjectOnPlane(posA - pivotA, ReferenceUp);
var b = Vector3.ProjectOnPlane(posB - pivotB, ReferenceUp);
var c = Vector3.Slerp(a, b, t);
posA = (posA - a) + c;
posB = (posB - b) + c;
}
else if ((BlendHint & BlendHintValue.SphericalPositionBlend) != 0)
{
// Spherical interpolation about pivot
var c = Vector3.Slerp(posA - pivotA, posB - pivotB, t);
posA = pivotA + c;
posB = pivotB + c;
}
}
return Vector3.Lerp(posA, posB, t);
}
}
}