using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
///
/// This structure holds the object that implements AutoDolly on a spline.
///
[Serializable]
public struct SplineAutoDolly
{
/// If set, will enable AutoDolly on a spline
[Tooltip("If set, will enable the selected automatic dolly along the spline")]
public bool Enabled;
/// This is the object that actually implements the AutoDolly
[SerializeReference]
public ISplineAutoDolly Method;
///
/// Interface for procedural spline dolly.
/// Implement this to provide a custom algorithm for choosing a point on the path.
///
public interface ISplineAutoDolly
{
/// Called from OnValidate() to validate the settings.
void Validate();
/// Call this to reset any state information contained in the implementation.
void Reset();
/// Returns true if this implementation requires a tracking target.
bool RequiresTrackingTarget { get; }
///
/// Compute the desired position on the spline.
///
/// The MonoBehaviour that is asking.
/// The target object (may be null for algorithms that don't require it).
/// The spline on which the location must be found.
/// The current position on the spline.
/// The units in which spline positions are expressed.
/// Current deltaTime. If smaller than 0, then previous frame data should be ignored.
/// The desired position on the spline, expressed in positionUnits.
float GetSplinePosition(
MonoBehaviour sender, Transform target, SplineContainer spline,
float currentPosition, PathIndexUnit positionUnits, float deltaTime);
}
///
/// ISplineAutoDolly implementation that moves the object at a constant speed align the spline.
///
[Serializable]
public class FixedSpeed : ISplineAutoDolly
{
/// Speed of travel, in current position units per second.
[Tooltip("Speed of travel, in current position units per second.")]
public float Speed;
/// Called from OnValidate() to validate the settings.
void ISplineAutoDolly.Validate() {}
/// This implementation does nothing.
void ISplineAutoDolly.Reset() {}
/// Returns true if this implementation requires a tracking target.
bool ISplineAutoDolly.RequiresTrackingTarget => false;
///
/// Compute the desired position on the spline.
///
/// The MonoBehaviour that is asking.
/// The target object (may be null for algorithms that don't require it).
/// The spline on which the location must be found.
/// The current position on the spline.
/// The units in which spline positions are expressed.
/// Current deltaTime. If smaller than 0, then previous frame data should be ignored.
/// The desired position on the spline, expressed in positionUnits.
float ISplineAutoDolly.GetSplinePosition(
MonoBehaviour sender, Transform target, SplineContainer spline,
float currentPosition, PathIndexUnit positionUnits, float deltaTime)
{
// Only works if playing
if (Application.isPlaying && spline.IsValid() && deltaTime > 0)
return currentPosition + Speed * deltaTime;
return currentPosition;
}
}
///
/// ISplineAutoDolly implementation that finds the point on th spline closest to the target.
/// Note that this is a simple stateless algorithm, and is not appropriate for all spline shapes.
/// For example, if the spline is forming an arc and the target is inside the arc, then the closest
/// point can be noisy or undefined. Consider for example a spline that is perfectly circular
/// with the target at the center. Where is the closest point?
///
[Serializable]
public class NearestPointToTarget : ISplineAutoDolly
{
///
/// Offset, in current position units, from the closest point on the spline to the follow target.
///
[Tooltip("Offset, in current position units, from the closest point on the spline to the follow target")]
public float PositionOffset = 0;
///
/// Affects how many segments to split a spline into when calculating the nearest point.
/// Higher values mean smaller and more segments, which increases accuracy at the cost of
/// processing time. In most cases, the default resolution is appropriate. Use
/// with to fine-tune point accuracy.
/// For more information, see SplineUtility.GetNearestPoint.
///
[Tooltip("Affects how many segments to split a spline into when calculating the nearest point. "
+ "Higher values mean smaller and more segments, which increases accuracy at the cost of "
+ "processing time. In most cases, the default value (4) is appropriate. Use with SearchIteration "
+ "to fine-tune point accuracy.")]
public int SearchResolution = 4;
///
/// The nearest point is calculated by finding the nearest point on the entire length
/// of the spline using to divide into equally spaced line segments.
/// Successive iterations will then subdivide further the nearest segment, producing more
/// accurate results. In most cases, the default value is sufficient.
/// For more information, see SplineUtility.GetNearestPoint.
///
[Tooltip("The nearest point is calculated by finding the nearest point on the entire "
+ "length of the spline using SearchResolution to divide into equally spaced line segments. "
+ "Successive iterations will then subdivide further the nearest segment, producing more "
+ "accurate results. In most cases, the default value (2) is sufficient.")]
public int SearchIteration = 2;
/// Called from OnValidate() to validate the settings.
void ISplineAutoDolly.Validate()
{
SearchResolution = Mathf.Max(SearchResolution, 1);
SearchIteration = Mathf.Max(SearchIteration, 1);
}
/// This implementation does nothing.
void ISplineAutoDolly.Reset() {}
/// Returns true if this implementation requires a tracking target.
bool ISplineAutoDolly.RequiresTrackingTarget => true;
///
/// Compute the desired position on the spline.
///
/// The MonoBehaviour that is asking.
/// The target object (may be null for algorithms that don't require it).
/// The spline on which the location must be found.
/// The current position on the spline.
/// The units in which spline positions are expressed.
/// Current deltaTime. If smaller than 0, then previous frame data should be ignored.
/// The desired position on the spline, expressed in positionUnits.
float ISplineAutoDolly.GetSplinePosition(
MonoBehaviour sender, Transform target, SplineContainer spline,
float currentPosition, PathIndexUnit positionUnits, float deltaTime)
{
if (target == null || !spline.IsValid())
return currentPosition;
// Convert target into spline local space, because SplineUtility works in spline local space
SplineUtility.GetNearestPoint(spline.Spline,
spline.transform.InverseTransformPoint(target.position), out _, out var normalizedPos,
SearchResolution, SearchIteration);
// GML hack because SplineUtility.GetNearestPoint is buggy
normalizedPos = Mathf.Clamp01(normalizedPos);
var pos = spline.Spline.ConvertIndexUnit(normalizedPos, PathIndexUnit.Normalized, positionUnits);
return pos + PositionOffset;
}
}
}
}