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