using UnityEngine; using System; using Cinemachine.Utility; namespace Cinemachine { /// Defines a world-space path, consisting of an array of waypoints, /// each of which has position, tangent, and roll settings. Bezier interpolation /// is performed between the waypoints, to get a smooth and continuous path. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)] [AddComponentMenu("Cinemachine/CinemachinePath")] [SaveDuringPlay] [DisallowMultipleComponent] [HelpURL(Documentation.BaseURL + "manual/CinemachinePath.html")] public class CinemachinePath : CinemachinePathBase { /// A waypoint along the path [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)] [Serializable] public struct Waypoint { /// Position in path-local space [Tooltip("Position in path-local space")] public Vector3 position; /// Offset from the position, which defines the tangent of the curve at the waypoint. /// The length of the tangent encodes the strength of the bezier handle. /// The same handle is used symmetrically on both sides of the waypoint, to ensure smoothness. [Tooltip("Offset from the position, which defines the tangent of the curve at the waypoint. " + "The length of the tangent encodes the strength of the bezier handle. The same handle " + "is used symmetrically on both sides of the waypoint, to ensure smoothness.")] public Vector3 tangent; /// Defines the roll of the path at this waypoint. /// The other orientation axes are inferred from the tangent and world up. [Tooltip("Defines the roll of the path at this waypoint. The other orientation axes are inferred from the tangent and world up.")] public float roll; } /// If checked, then the path ends are joined to form a continuous loop [Tooltip("If checked, then the path ends are joined to form a continuous loop.")] public bool m_Looped; /// The waypoints that define the path. /// They will be interpolated using a bezier curve [Tooltip("The waypoints that define the path. They will be interpolated using a bezier curve.")] public Waypoint[] m_Waypoints = Array.Empty(); /// The minimum value for the path position public override float MinPos => 0; /// The maximum value for the path position public override float MaxPos { get { int count = m_Waypoints.Length - 1; if (count < 1) return 0; return m_Looped ? count + 1 : count; } } /// True if the path ends are joined to form a continuous loop public override bool Looped => m_Looped; private void Reset() { m_Looped = false; m_Waypoints = new Waypoint[2] { new Waypoint { position = new Vector3(0, 0, -5), tangent = new Vector3(1, 0, 0) }, new Waypoint { position = new Vector3(0, 0, 5), tangent = new Vector3(1, 0, 0) } }; m_Appearance = new Appearance(); InvalidateDistanceCache(); } private void OnValidate() { InvalidateDistanceCache(); } /// When calculating the distance cache, sample the path this many /// times between points public override int DistanceCacheSampleStepsPerSegment => m_Resolution; /// Returns normalized position float GetBoundingIndices(float pos, out int indexA, out int indexB) { pos = StandardizePos(pos); int rounded = Mathf.RoundToInt(pos); if (Mathf.Abs(pos - rounded) < UnityVectorExtensions.Epsilon) indexA = indexB = (rounded == m_Waypoints.Length) ? 0 : rounded; else { indexA = Mathf.FloorToInt(pos); if (indexA >= m_Waypoints.Length) { pos -= MaxPos; indexA = 0; } indexB = Mathf.CeilToInt(pos); if (indexB >= m_Waypoints.Length) indexB = 0; } return pos; } /// Get a worldspace position of a point along the path /// Postion along the path. Need not be normalized. /// Local-space position of the point along at path at pos public override Vector3 EvaluateLocalPosition(float pos) { var result = Vector3.zero; if (m_Waypoints.Length > 0) { pos = GetBoundingIndices(pos, out var indexA, out var indexB); if (indexA == indexB) result = m_Waypoints[indexA].position; else { // interpolate var wpA = m_Waypoints[indexA]; var wpB = m_Waypoints[indexB]; result = SplineHelpers.Bezier3(pos - indexA, m_Waypoints[indexA].position, wpA.position + wpA.tangent, wpB.position - wpB.tangent, wpB.position); } } return result; } /// Get the tangent of the curve at a point along the path. /// Postion along the path. Need not be normalized. /// Local-space direction of the path tangent. /// Length of the vector represents the tangent strength public override Vector3 EvaluateLocalTangent(float pos) { var result = Vector3.forward; if (m_Waypoints.Length > 0) { pos = GetBoundingIndices(pos, out var indexA, out var indexB); if (indexA == indexB) result = m_Waypoints[indexA].tangent; else { var wpA = m_Waypoints[indexA]; var wpB = m_Waypoints[indexB]; result = SplineHelpers.BezierTangent3(pos - indexA, m_Waypoints[indexA].position, wpA.position + wpA.tangent, wpB.position - wpB.tangent, wpB.position); } } return result; } /// Get the orientation the curve at a point along the path. /// Postion along the path. Need not be normalized. /// Local-space orientation of the path, as defined by tangent, up, and roll. public override Quaternion EvaluateLocalOrientation(float pos) { var result = Quaternion.identity; if (m_Waypoints.Length > 0) { pos = GetBoundingIndices(pos, out var indexA, out var indexB); var fwd = EvaluateLocalTangent(pos); if (!fwd.AlmostZero()) result = Quaternion.LookRotation(fwd) * RollAroundForward(GetRoll(indexA, indexB, pos)); } return result; } internal float GetRoll(int indexA, int indexB, float standardizedPos) { if (indexA == indexB) return m_Waypoints[indexA].roll; float rollA = m_Waypoints[indexA].roll; float rollB = m_Waypoints[indexB].roll; if (indexB == 0) { // Special handling at the wraparound - cancel the spins rollA %= 360; rollB %= 360; } return Mathf.Lerp(rollA, rollB, standardizedPos - indexA); } // 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)); } } }