using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
///
/// CinemachineSplineDollyLookAtTargets is a component that allows the camera to look at
/// specific points in the world as it moves along a spline.
///
[ExecuteAlways, SaveDuringPlay]
[CameraPipeline(CinemachineCore.Stage.Aim)]
[AddComponentMenu("Cinemachine/Procedural/Rotation Control/Cinemachine Spline Dolly LookAt Targets")]
[DisallowMultipleComponent]
[HelpURL(Documentation.BaseURL + "manual/CinemachineSplineDollyLookAtTargets.html")]
public class CinemachineSplineDollyLookAtTargets : CinemachineComponentBase
{
/// LookAt targets for the camera at specific positions on the Spline
[Serializable]
public struct Item
{
/// The target object to look at. It may be None, in which case the Offset will specify a point in world spac
[Tooltip("The target object to look at. It may be None, in which case the Offset will specify a point in world space.")]
public Transform LookAt;
/// The offset (in local coords) from the LookAt target's origin. If LookAt target is None, this will specify a world-space point
[Tooltip("The offset (in local coords) from the LookAt target's origin. If LookAt target is None, this will specify a world-space point.")]
public Vector3 Offset;
/// Easing value for the Bezier curve. 0 is linear, 1 is smooth.
[Tooltip("Controls how to ease in and out of this data point. A value of 0 will linearly interpolate between "
+ "LookAt points, while a value of 1 will slow down and briefly pause the rotation to look at the target.")]
[Range(0, 1)]
public float Easing;
/// Get/set the LookAt point in world space.
public Vector3 WorldLookAt
{
readonly get => LookAt == null ? Offset : LookAt.TransformPoint(Offset);
set => Offset = LookAt == null ? value : LookAt.InverseTransformPoint(value);
}
}
/// Interpolator for the Targets
internal struct LerpItem : IInterpolator-
{
public Item Interpolate(Item a, Item b, float t)
{
var t2 = t * t;
var d = 1f - t;
t = 3f * d * d * t * Mathf.Lerp(0.3333f, 0, a.Easing) + 3f * d * t2 * Mathf.Lerp(0.6666f, 1, b.Easing) + t * t2;
return new Item { Offset = Vector3.Lerp(a.WorldLookAt, b.WorldLookAt, t) };
}
}
/// LookAt targets for the camera at specific positions on the Spline
/// It is not recommended to modify the data array at runtime, because the infrastructure
/// expects the array to be in strictly increasing order of distance along the spline. If you do change
/// the array at runtime, you must take care to keep it in this order, or the results will be unpredictable.
///
[Tooltip("LookAt targets for the camera at specific positions on the Spline")]
public SplineData
- Targets = new () { DefaultValue = new Item { Easing = 1 } };
void Reset() => Targets = new SplineData
- { DefaultValue = new Item { Easing = 1 } };
///
public override bool IsValid => enabled && Targets != null && GetGetSplineAndDolly(out _, out _);
///
public override CinemachineCore.Stage Stage => CinemachineCore.Stage.Aim;
///
public override void MutateCameraState(ref CameraState state, float deltaTime)
{
if (!GetGetSplineAndDolly(out _, out var dolly))
return;
var spline = dolly.SplineSettings.GetCachedSpline();
var item = Targets.Evaluate(spline, dolly.CameraPosition, dolly.PositionUnits, new LerpItem());
var dir = item.WorldLookAt - state.RawPosition;
if (dir.sqrMagnitude > UnityVectorExtensions.Epsilon)
{
var up = state.ReferenceUp;
if (Vector3.Cross(dir, up).sqrMagnitude < UnityVectorExtensions.Epsilon)
{
// Look direction is parallel to the up vector
up = state.RawOrientation * Vector3.back;
if (Vector3.Cross(dir, up).sqrMagnitude < UnityVectorExtensions.Epsilon)
up = state.RawOrientation * Vector3.left;
}
state.RawOrientation = Quaternion.LookRotation(dir, up);
}
state.ReferenceLookAt = item.Offset;
}
///
/// API for the inspector: Get the spline and the required CinemachineTrackDolly component.
///
/// The spline being augmented
/// The associated CinemachineTrackDolly component
///
internal bool GetGetSplineAndDolly(out SplineContainer spline, out CinemachineSplineDolly dolly)
{
dolly = null;
if (this != null && TryGetComponent(out dolly))
{
spline = dolly.Spline;
return spline != null && spline.Spline != null;
}
spline = null;
return false;
}
}
}