using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
///
/// Extension that can be added to a SplineContainer or a CinemachineCamera that uses a SplineContainer, for example a CinemachineCamera
/// that has SplineDolly as Body component.
/// - When CinemachineSplineRoll is added to a gameObject that has SplineContainer,
/// then the roll affects any CinemachineCamera that reads that SplineContainer globally.
/// - When CinemachineSplineRoll is added to a CinemachineCamera, then roll only affects that CinemachineCamera locally.
///
[ExecuteInEditMode]
[DisallowMultipleComponent]
[AddComponentMenu("Cinemachine/Helpers/Cinemachine Spline Roll")]
[HelpURL(Documentation.BaseURL + "manual/CinemachineSplineRoll.html")]
[SaveDuringPlay]
public class CinemachineSplineRoll : MonoBehaviour, ISerializationCallbackReceiver
{
/// Structure to hold roll value for a specific location on the track.
[Serializable]
public struct RollData
{
///
/// Roll (in degrees) around the forward direction for specific location on the track.
/// When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.
/// When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.
///
[Tooltip("Roll (in degrees) around the forward direction for specific location on the track.\n" +
"- When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.\n" +
"- When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.")]
public float Value;
/// Implicit conversion to float
/// The RollData setting to convert.
/// The value of the RollData setting.
public static implicit operator float(RollData roll) => roll.Value;
/// Implicit conversion from float
/// The value with which to initialize the RollData setting.
/// A new RollData setting with the given value.
public static implicit operator RollData(float roll) => new () { Value = roll };
}
///
/// When enabled, roll eases into and out of the data point values. Otherwise, interpolation is linear.
///
[Tooltip("When enabled, roll eases into and out of the data point values. Otherwise, interpolation is linear.")]
public bool Easing = true;
///
/// Get the appropriate interpolator for the RollData, depending on the Easing setting
///
/// The appropriate interpolator for the RollData, depending on the Easing setting.
public IInterpolator GetInterpolator() => Easing ? new LerpRollDataWithEasing() : new LerpRollData();
/// Interpolator for the RollData, with no easing between data points.
public struct LerpRollData : IInterpolator
{
///
public RollData Interpolate(RollData a, RollData b, float t) => new() { Value = Mathf.Lerp(a.Value, b.Value, t) };
}
/// Interpolator for the RollData, with easing between data points
public struct LerpRollDataWithEasing : IInterpolator
{
///
public RollData Interpolate(RollData a, RollData b, float t)
{
var t2 = t * t;
var d = 1f - t;
t = 3f * d * t2 + t * t2;
return new() { Value = Mathf.Lerp(a.Value, b.Value, t) };
}
}
///
/// Roll (in degrees) around the forward direction for specific location on the track.
/// When placed on a SplineContainer, this is going to be a global override that affects all vcams using the Spline.
/// When placed on a CinemachineCamera, this is going to be a local override that only affects that CinemachineCamera.
///
///
/// It is not recommended to modify the data array at runtime, because the infrastructure
/// expects the array to be in strclty 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.
///
[HideFoldout]
public SplineData Roll;
#if UNITY_EDITOR
// Only needed for drawing the gizmo
internal ISplineContainer SplineContainer
{
get
{
// In case behaviour was re-parented in the editor, we check every time
if (TryGetComponent(out ISplineReferencer referencer))
return referencer.SplineSettings.Spline == null || referencer.SplineSettings.Spline.Splines.Count == 0
? null : referencer.SplineSettings.Spline;
if (TryGetComponent(out ISplineContainer container))
return container.Splines.Count > 0 ? container : null;
return null;
}
}
#endif
//============================================================================
// Legacy streaming support
[HideInInspector, SerializeField, NoSaveDuringPlay]
int m_StreamingVersion;
void PerformLegacyUpgrade(int streamedVersion)
{
if (streamedVersion < 20240101)
{
// roll values were inverted
for (int i = 0; i < Roll.Count; ++i)
{
var item = Roll[i];
item.Value = -item.Value;
Roll[i] = item;
}
}
}
//============================================================================
void Reset()
{
Roll?.Clear();
Easing = true;
}
void OnEnable() {} // Needed so we can disable it in the editor
///
public void OnBeforeSerialize() {}
///
public void OnAfterDeserialize()
{
// Perform legacy upgrade if necessary
if (m_StreamingVersion < CinemachineCore.kStreamingVersion)
PerformLegacyUpgrade(m_StreamingVersion);
m_StreamingVersion = CinemachineCore.kStreamingVersion;
}
/// Cache for clients that use CinemachineSplineRoll
internal struct RollCache
{
CinemachineSplineRoll m_RollCache;
public void Refresh(MonoBehaviour owner)
{
m_RollCache = null;
// Check if owner has CinemachineSplineRoll
if (!owner.TryGetComponent(out m_RollCache) && owner is ISplineReferencer referencer)
{
// Check if the spline has CinemachineSplineRoll
var spline = referencer.SplineSettings.Spline;
if (spline != null && spline is Component component)
component.TryGetComponent(out m_RollCache);
}
}
public CinemachineSplineRoll GetSplineRoll(MonoBehaviour owner)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
Refresh(owner);
#endif
return m_RollCache;
}
}
}
}