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; } } }