using UnityEngine; using System; using Unity.Mathematics; using UnityEngine.Splines; namespace Unity.Cinemachine { /// /// This behaviour can be attached to a GameObject with a SplineContainer. /// It proivdes a function to apply smoothing to the spline. /// Smoothing auto-adjusts the knot tangents to maintain second-order smoothness of the spline, making it suitable /// for camera paths. /// /// Smoothing is costly, because the entire path has to be considered when adjusting each knot. /// /// In Editor mode, an option is provided to automatically smooth the spline whenever it is modified. /// In runtime mode, smoothing must be invoked manually by calling SmoothSplineNow(). /// [ExecuteAlways] [AddComponentMenu("Cinemachine/Helpers/Cinemachine Spline Smoother")] [HelpURL(Documentation.BaseURL + "manual/CinemachineSplineSmoother.html")] [RequireComponent(typeof(SplineContainer))] public class CinemachineSplineSmoother : MonoBehaviour { /// /// If checked, the spline will be automatically smoothed whenever it is modified. /// [Tooltip("If checked, the spline will be automatically smoothed whenever it is modified (editor only).")] public bool AutoSmooth = true; #if UNITY_EDITOR internal static Action OnEnableCallback; internal static Action OnDisableCallback; void OnEnable() => OnEnableCallback?.Invoke(this); void OnDisable() => OnDisableCallback?.Invoke(this); // Editor only, implements auto-smooth internal void OnSplineModified(Spline spline) { if (enabled && AutoSmooth && TryGetComponent(out SplineContainer container) && spline == container.Spline) SmoothSplineNow(); } #endif /// /// Apply smoothing to the spline. /// Knot settings will be adjusted to produce second-order smoothness of the path, making it /// suitable for use as a camera path. /// /// This is an expensive operation. Use with caution. /// public void SmoothSplineNow() { if (TryGetComponent(out var container) && container.Spline != null) { var spline = container.Spline; int numPoints = spline.Count; if (numPoints < 3) return; // doesn't work if too few points float3[] p1 = new float3[numPoints]; float3[] p2 = new float3[numPoints]; float3[] knots = new float3[numPoints]; for (int i = 0; i < numPoints; ++i) knots[i] = spline[i].Position; if (spline.Closed) SplineHelpers.ComputeSmoothControlPointsLooped(ref knots, ref p1, ref p2); else SplineHelpers.ComputeSmoothControlPoints(ref knots, ref p1, ref p2); for (int i = 0; i < numPoints; i++) { spline.SetTangentMode(i, TangentMode.Mirrored); var knot = spline[i]; var up = math.mul(knot.Rotation, new float3(0, 1, 0)); var fwd = p1[i] - knots[i]; var back = p2[i > 0 ? i - 1 : numPoints - 1] - knots[i]; var len = (math.length(back) + math.length(fwd)) * 0.5f; knot.Rotation = quaternion.LookRotationSafe(fwd, up); knot.TangentIn = (i == 0 && !spline.Closed) ? default : new float3(0, 0, -len); knot.TangentOut = (i == numPoints - 1 && !spline.Closed) ? default : new float3(0, 0, len); spline[i] = knot; } } } } }