using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
/// Interface for behaviours that reference a Spline
public interface ISplineReferencer
{
/// Get a reference to the SplineSettings struct contained in this object.
/// A reference to the embedded SplineSettings struct
public ref SplineSettings SplineSettings { get; }
}
///
/// This structure holds the spline reference and the position and position units.
///
[Serializable]
public struct SplineSettings
{
/// The Spline container to which the the position will apply.
[Tooltip("The Spline container to which the position will apply.")]
public SplineContainer Spline;
/// The position along the spline. The actual value corresponding to a given point
/// on the spline will depend on the unity type.
[NoSaveDuringPlay]
[Tooltip("The position along the spline. The actual value corresponding to a given point "
+ "on the spline will depend on the unity type.")]
public float Position;
/// How to interpret the Spline Position:
/// - Distance: Values range from 0 (start of Spline) to Length of the Spline (end of Spline).
/// - Normalized: Values range from 0 (start of Spline) to 1 (end of Spline).
/// - Knot: Values are defined by knot indices and a fractional value representing the normalized
/// interpolation between the specific knot index and the next knot."
[Tooltip("How to interpret the Spline Position:\n"
+ "- Distance: Values range from 0 (start of Spline) to Length of the Spline (end of Spline).\n"
+ "- Normalized: Values range from 0 (start of Spline) to 1 (end of Spline).\n"
+ "- Knot: Values are defined by knot indices and a fractional value representing the normalized "
+ "interpolation between the specific knot index and the next knot.\n")]
public PathIndexUnit Units;
///
/// Change the units of the position, preserving the position on the spline.
/// The value of Position may change in order to preserve the position on the spline.
///
/// The new units to use
public void ChangeUnitPreservePosition(PathIndexUnit newUnits)
{
if (Spline.IsValid() && newUnits != Units)
Position = GetCachedSpline().ConvertIndexUnit(Position, Units, newUnits);
Units = newUnits;
}
CachedScaledSpline m_CachedSpline;
int m_CachedFrame;
///
/// Computing spline length dynamically is costly. This method computes the length on the first call
/// and caches it for subsequent calls.
///
/// While we can auto-detect changes to the transform and some changes to the spline's knots, it would be
/// too costly to continually check for subtle changes to the spline's control points. Therefore, if such
/// subtle changes are made to the spline's control points at runtime, client is responsible
/// for calling InvalidateCache().
///
/// Cached version of the spline with transform incorporated
internal CachedScaledSpline GetCachedSpline()
{
if (!Spline.IsValid())
InvalidateCache();
else
{
// Only check crude validity once per frame, to keep things efficient
if (m_CachedSpline == null || (Time.frameCount != m_CachedFrame && !m_CachedSpline.IsCrudelyValid(Spline.Spline, Spline.transform)))
{
InvalidateCache();
m_CachedSpline = new CachedScaledSpline(Spline.Spline, Spline.transform);
}
#if UNITY_EDITOR
// Deep check only in editor and if not playing
else if (!Application.isPlaying && Time.frameCount != m_CachedFrame && !m_CachedSpline.KnotsAreValid(Spline.Spline, Spline.transform))
{
InvalidateCache();
m_CachedSpline = new CachedScaledSpline(Spline.Spline, Spline.transform);
}
#endif
m_CachedFrame = Time.frameCount;
}
return m_CachedSpline;
}
///
/// While we can auto-detect changes to the transform and some changes to the spline's knots, it would be
/// too costly to continually check for subtle changes to the spline's control points. Therefore, if such
/// subtle changes are made to the spline's control points at runtime, client is responsible
/// for calling InvalidateCache().
///
public void InvalidateCache()
{
m_CachedSpline?.Dispose();
m_CachedSpline = null;
}
}
///
/// In order to properly handle the spline scale, we need to cache a spline that incorporates the scale
/// natively. This class does that.
/// Be sure to call Dispose() before discarding this object, otherwise there will be memory leaks.
///
internal class CachedScaledSpline : ISpline, IDisposable
{
NativeSpline m_NativeSpline;
readonly Spline m_CachedSource;
//readonly float m_CachedRawLength;
readonly Vector3 m_CachedScale;
bool m_IsAllocated;
/// Construct a CachedScaledSpline
/// The spline to cache
/// The transform to use for the scale, or null
/// The allocator to use for the native spline
public CachedScaledSpline(Spline spline, Transform transform, Allocator allocator = Allocator.Persistent)
{
var scale = transform != null ? transform.lossyScale : Vector3.one;
m_CachedSource = spline;
m_NativeSpline = new NativeSpline(spline, Matrix4x4.Scale(scale), allocator);
//m_CachedRawLength = spline.GetLength();
m_CachedScale = scale;
m_IsAllocated = true;
}
///
public void Dispose()
{
if (m_IsAllocated)
m_NativeSpline.Dispose();
m_IsAllocated = false;
}
/// Check if the cached spline is still valid, without doing any costly knot checks.
/// The source spline
/// The source spline's transform, or null
/// True if the spline is crudely unchanged
public bool IsCrudelyValid(Spline spline, Transform transform)
{
var scale = transform != null ? transform.lossyScale : Vector3.one;
return spline == m_CachedSource && (m_CachedScale - scale).AlmostZero()
&& m_NativeSpline.Count == m_CachedSource.Count
//&& Mathf.Abs(m_CachedRawLength - spline.GetLength()) < 0.001f; // this would catch almost everything but is it too expensive?
;
}
/// Performs costly knot check to see if the spline's knots have changed.
/// The source spline
/// The source spline's transform, or null
/// True if the knots have not changed
public bool KnotsAreValid(Spline spline, Transform transform)
{
if (m_NativeSpline.Count != spline.Count)
return false;
var m = Matrix4x4.Scale(transform != null ? transform.lossyScale : Vector3.one);
var ita = GetEnumerator();
var itb = spline.GetEnumerator();
while (ita.MoveNext() && itb.MoveNext())
if (!ita.Current.Equals(itb.Current.Transform(m)))
return false;
return true;
}
///
public BezierKnot this[int index] => m_NativeSpline[index];
///
public bool Closed => m_NativeSpline.Closed;
///
public int Count => m_NativeSpline.Count;
///
public BezierCurve GetCurve(int index) => m_NativeSpline.GetCurve(index);
///
public float GetCurveInterpolation(int curveIndex, float curveDistance) => m_NativeSpline.GetCurveInterpolation(curveIndex, curveDistance);
///
public float GetCurveLength(int index) => m_NativeSpline.GetCurveLength(index);
#if CINEMACHINE_SPLINES_2_4
///
public float3 GetCurveUpVector(int index, float t) => m_NativeSpline.GetCurveUpVector(index, t);
#endif
///
public IEnumerator GetEnumerator() => m_NativeSpline.GetEnumerator();
///
public float GetLength() => m_NativeSpline.GetLength();
///
IEnumerator IEnumerable.GetEnumerator() => m_NativeSpline.GetEnumerator();
}
}