using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Cinemachine
{
///
/// A collection of helpers for UnityEngine Spline.
///
static class SplineContainerExtensions
{
/// Check spline container and child spline for null
/// spline container to check
/// true if container holds a non-null spline
public static bool IsValid(this SplineContainer spline) => spline != null && spline.Spline != null;
///
/// Apply to a additional roll from
///
/// The spline in question
/// The additional roll to apply
/// The normalized position on the spline
/// returned point on the spline, in spline-local coords
/// returned rotation at the point on the spline, in spline-local coords
/// True if the spline position is valid
public static bool LocalEvaluateSplineWithRoll(
this SplineContainer spline,
CinemachineSplineRoll roll,
Quaternion defaultRotation,
float tNormalized,
out Vector3 position, out Quaternion rotation)
{
if (spline.Spline == null || !SplineUtility.Evaluate(
spline.Spline, tNormalized, out var localPosition, out var localTangent, out var localUp))
{
position = Vector3.zero;
rotation = Quaternion.identity;
return false;
}
position = localPosition;
Vector3 fwd = localTangent;
Vector3 up = localUp;
// Try to fix tangent when 0
if (fwd.AlmostZero())
{
const float delta = 0.01f;
var atEnd = tNormalized > 1.0f - delta;
var t1 = atEnd ? tNormalized - delta : tNormalized + delta;
var p = spline.EvaluatePosition(t1);
fwd = atEnd ? localPosition - p : p - localPosition;
}
// Use supplied defaults if spline rotation is still undefined
var cross = Vector3.Cross(fwd, up);
if (cross.AlmostZero() || cross.IsNaN())
{
fwd = defaultRotation * Vector3.forward;
up = defaultRotation * Vector3.up;
}
rotation = Quaternion.LookRotation(fwd, up);
// Apply extra roll
if (roll != null && roll.enabled)
{
float rollValue = roll.Roll.Evaluate(spline.Spline, tNormalized,
PathIndexUnit.Normalized, new UnityEngine.Splines.Interpolators.LerpFloat());
rotation = Quaternion.AngleAxis(-rollValue, fwd) * rotation;
}
return true;
}
///
/// Apply to a additional roll from
///
/// The spline in question
/// The additional roll to apply
/// The normalized position on the spline
/// returned point on the spline, in world coords
/// returned rotation at the point on the spline, in world coords
/// True if the spline position is valid
public static bool EvaluateSplineWithRoll(
this SplineContainer spline,
CinemachineSplineRoll roll,
Quaternion defaultRotation,
float tNormalized,
out Vector3 position, out Quaternion rotation)
{
var result = LocalEvaluateSplineWithRoll(spline, roll, defaultRotation, tNormalized, out position, out rotation);
position = spline.transform.TransformPoint(position);
rotation = spline.transform.rotation * rotation;
return result;
}
///
/// Get the maximum value for the spline position. Minimum value is always 0.
///
/// The spline in question
/// The spline position is expressed in these units
/// The length of the spline, in distance units.
/// Passed as parameter for efficiency because length calculation is slow.
/// If a negative value is passed, length will be calculated.
///
public static float GetMaxPosition(
this Spline spline, PathIndexUnit unit, float splineLength = -1)
{
switch (unit)
{
case PathIndexUnit.Distance:
return splineLength < 0 ? spline.GetLength() : splineLength;
case PathIndexUnit.Knot:
{
var knotCount = spline.Count;
return (!spline.Closed || knotCount < 2) ? Mathf.Max(0, knotCount - 1) : knotCount;
}
}
return 1;
}
///
/// Clamp spline position to min and max values, respecting loop wraparound for closed paths.
///
/// The spline in question
/// Spline position to sanitize
/// The spline position is expressed in these units
/// The length of the spline, in distance units.
/// Passed as parameter for efficiency because length calculation is slow.
/// If a negative value is passed, length will be calculated.
/// The clamped position value, respecting the specified units
public static float StandardizePosition(
this Spline spline, float t, PathIndexUnit unit, float splineLength = -1)
{
var max = spline.GetMaxPosition(unit, splineLength);
if (!spline.Closed)
return Mathf.Clamp(t, 0, max);
t %= max;
if (t < 0)
t += max;
return t;
}
}
}