using Unity.Mathematics;
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 ISplineContainer spline) => spline != null && spline.Splines != null && spline.Splines.Count > 0;
///
/// Apply to a additional roll from
///
/// The spline in question
/// The normalized position on the spline
/// The additional roll to apply, or null
/// 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 ISpline spline,
float tNormalized,
CinemachineSplineRoll roll,
out Vector3 position, out Quaternion rotation)
{
if (spline == null || !SplineUtility.Evaluate(
spline, tNormalized, out var splinePosition, out var fwd, out var up))
{
position = Vector3.zero;
rotation = Quaternion.identity;
return false;
}
// Use defaults if spline rotation is undefined
var cross = Vector3.Cross(fwd, up);
if (cross.AlmostZero() || cross.IsNaN())
{
fwd = Vector3.forward;
up = Vector3.up;
}
// Apply extra roll if present
if (roll == null || !roll.enabled)
rotation = Quaternion.LookRotation(fwd, up);
else
{
float rollValue = roll.Roll.Evaluate(spline, tNormalized, PathIndexUnit.Normalized, roll.GetInterpolator());
rotation = Quaternion.LookRotation(fwd, up) * RollAroundForward(rollValue);
// same as Quaternion.AngleAxis(roll, Vector3.forward), just simplified
static Quaternion RollAroundForward(float angle)
{
float halfAngle = angle * 0.5F * Mathf.Deg2Rad;
return new Quaternion(0, 0, Mathf.Sin(halfAngle), Mathf.Cos(halfAngle));
}
}
position = splinePosition;
return true;
}
///
/// Apply to a additional roll from
///
/// The spline in question
/// The transform of the spline
/// The normalized position on the spline
/// The additional roll to apply, or null
/// 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 ISpline spline,
Transform transform,
float tNormalized,
CinemachineSplineRoll roll,
out Vector3 position, out Quaternion rotation)
{
var result = LocalEvaluateSplineWithRoll(spline, tNormalized, roll, out position, out rotation);
position = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).MultiplyPoint3x4(position);
rotation = transform.rotation * rotation;
return result;
}
/// Evaluate a spline's world position at a normalized spline index
/// The spline in question
/// The transform of the spline, or null
/// The normalized position on the spline
/// True if the spline position is valid
public static Vector3 EvaluateSplinePosition(
this ISpline spline, Transform transform, float tNormalized)
{
float3 position = spline == null ? default : SplineUtility.EvaluatePosition(spline, tNormalized);
return transform == null ? position : Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).MultiplyPoint3x4(position);
}
///
/// 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
/// This is needed because we don't have access to the spline's scale.
///
public static float GetMaxPosition(this ISpline spline, PathIndexUnit unit)
{
switch (unit)
{
case PathIndexUnit.Distance:
return spline.GetLength();
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
/// This is needed because we don't have access to the spline's scale.
/// The clamped position value, respecting the specified units
public static float StandardizePosition(this ISpline spline, float t, PathIndexUnit unit, out float maxPos)
{
maxPos = spline.GetMaxPosition(unit);
if (float.IsNaN(t))
return 0;
if (!spline.Closed)
return Mathf.Clamp(t, 0, maxPos);
t %= maxPos;
if (t < 0)
t += maxPos;
return t;
}
}
}