using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Mathematics; namespace UnityEngine.Splines { // IMPORTANT: // SplinePathRef is the serializable equivalent of SplinePath. It is intentionally not public due to // questions around tooling and user interface. Left here because it is used for tests and debugging. // ...and if it needs to be stated, the name is terrible and should obviously be replaced before becoming public. [Serializable] class SplinePathRef { /// /// SliceRef represents a partial or complete range of curves from another . This is a /// serializable type, intended to be used with . To create an evaluable /// from a and , use . /// A by itself does not store any s. It stores a reference to /// a separate index within a , then retrieves knots by iterating /// the . /// Use in conjunction with to create seamless paths from /// discrete segments. /// [Serializable] public class SliceRef { /// /// The index in the array of the referenced . /// [SerializeField] public int Index; /// /// An inclusive start index, number of indices, and direction to iterate. /// [SerializeField] public SplineRange Range; /// /// Constructor for a new . /// /// The index in the array of the referenced . /// An inclusive start index, number of indices, and direction to iterate. public SliceRef(int splineIndex, SplineRange range) { Index = splineIndex; Range = range; } } [SerializeField] public SliceRef[] Splines; public SplinePathRef() { } public SplinePathRef(IEnumerable slices) { Splines = slices.ToArray(); } } /// /// The SplinePath type is an implementation of that is composed of multiple sections of /// other splines (see ). This is useful when you want to evaluate a path that follows /// multiple splines, typically in the case where splines share linked knots. /// /// This class is a data structure that defines the range of curves to associate together. This class is not meant to be /// used intensively for runtime evaluation because it is not performant. Data is not meant to be /// stored in that struct and that struct is not reactive to spline changes. The GameObject that contains this /// slice can be scaled and the knots of the targeted spline that can moved around the curve length cannot be stored /// here so evaluating positions, tangents and up vectors is expensive. /// /// If performance is a critical requirement, create a new or /// from your . Note that you might pass a /// to constructors for both and . /// /// /// /// public class SplinePath : SplinePath> { /// /// Creates a new from a collection of . /// /// The splines to create this path with. public SplinePath(IEnumerable> slices) : base(slices) { } } /// /// The SplinePath type is an implementation of that is composed of multiple sections of /// other splines (see ). This is useful when you want to evaluate a path that follows /// multiple splines, typically in the case where splines share linked knots. /// /// If performance is a critical requirement, create a new or /// from your . Note that you might pass a /// to constructors for both and . /// /// /// /// /// The type of spline to create a path with. public class SplinePath : ISpline, IHasEmptyCurves where T : ISpline { T[] m_Splines; int[] m_Splits; /// /// The splines that make up this path. /// public IReadOnlyList Slices { get => m_Splines; set { m_Splines = value.ToArray(); BuildSplitData(); } } /// /// Create a new from a collection of . /// /// A collection of . public SplinePath(IEnumerable slices) { m_Splines = slices.ToArray(); BuildSplitData(); } void BuildSplitData() { m_Splits = new int[m_Splines.Length]; for (int i = 0, c = m_Splits.Length, k = 0; i < c; ++i) m_Splits[i] = (k += (m_Splines[i].Count + (m_Splines[i].Closed ? 1 : 0))) - 1; } /// /// Gets an enumerator that iterates through the collection. /// /// An IEnumerator that is used to iterate the collection. public IEnumerator GetEnumerator() { foreach (var branch in m_Splines) foreach (var knot in branch) yield return knot; } /// /// Gets an enumerator that iterates through the collection. /// /// An IEnumerator that is used to iterate the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Returns the number of knots. /// Note that there are duplicate knots where two meet. /// In addition, each closed have their first knot duplicated. /// Use to access curves rather than construct the curve yourself. /// public int Count { get { var count = 0; foreach (var spline in m_Splines) count += (spline.Count + (spline.Closed ? 1 : 0)); return count; } } /// /// Gets the knot at . If the section that contains this /// knot has a with , the in and out tangents /// are reversed. /// /// The zero-based index of the element to get. public BezierKnot this[int index] => this[GetBranchKnotIndex(index)]; /// /// Gets the knot at . If the segment that contains this /// knot has a with , the in and out tangents /// are reversed. /// /// The zero-based index of the slice and knot to get. public BezierKnot this[SplineKnotIndex index] { get { var spline = m_Splines[index.Spline]; var knotIndex = spline.Closed ? index.Knot % spline.Count : index.Knot; return spline[knotIndex]; } } // used by tests internal SplineKnotIndex GetBranchKnotIndex(int knot) { knot = Closed ? knot % Count : math.clamp(knot, 0, Count); for (int i = 0, offset = 0; i < m_Splines.Length; i++) { var slice = m_Splines[i]; var sliceCount = slice.Count + (slice.Closed ? 1 : 0); if (knot < offset + sliceCount) return new SplineKnotIndex(i, math.max(0, knot - offset)); offset += sliceCount; } return new SplineKnotIndex(m_Splines.Length - 1, m_Splines[^1].Count - 1); } /// /// does not support Closed splines. /// public bool Closed => false; /// /// Return the sum of all curve lengths, accounting for state. /// /// /// Returns the sum length of all curves composing this spline, accounting for closed state. /// public float GetLength() { var length = 0f; for (int i = 0, c = Closed ? Count : Count - 1; i < c; ++i) length += GetCurveLength(i); return length; } /// /// A collection of knot indices that should be considered degenerate curves for the purpose of creating a /// non-interpolated gap between curves. /// public IReadOnlyList EmptyCurves => m_Splits; bool IsDegenerate(int index) { // because splits are set up by this class, we know that indices are sorted int split = Array.BinarySearch(m_Splits, index); if (split < 0) return false; return true; } /// /// Gets a from a knot index. This function returns /// degenerate (0 length) curves at the overlap points between each . /// /// The knot index that is the first control point for this curve. /// /// A formed by the knot at index and the next knot. /// public BezierCurve GetCurve(int knot) { var index = GetBranchKnotIndex(knot); if (IsDegenerate(knot)) { var point = new BezierKnot(this[index].Position); return new BezierCurve(point, point); } BezierKnot a = this[index], b = this.Next(knot); return new BezierCurve(a, b); } /// /// Returns the length of a curve. This function returns 0 length for knot indices where /// segments overlap. /// /// The index of the curve that the length is retrieved from. /// /// /// Returns the length of the curve of index 'index' in the spline. /// public float GetCurveLength(int index) { if(IsDegenerate(index)) return 0f; var knot = GetBranchKnotIndex(index); var slice = m_Splines[knot.Spline]; if (knot.Spline >= m_Splines.Length - 1 && knot.Knot >= slice.Count - 1) return CurveUtility.CalculateLength(GetCurve(index)); return slice.GetCurveLength(knot.Knot); } /// /// Return the up vector for a t ratio on the curve. /// /// The index of the curve for which the length needs to be retrieved. /// A value between 0 and 1 representing the ratio along the curve. /// /// Returns the up vector at the t ratio of the curve of index 'index'. /// public float3 GetCurveUpVector(int index, float t) { if(IsDegenerate(index)) return 0f; var knot = GetBranchKnotIndex(index); var slice = m_Splines[knot.Spline]; // Closing curve if (knot.Spline >= m_Splines.Length - 1 && knot.Knot >= slice.Count - 1) { BezierKnot a = this[knot], b = this.Next(index); var curve = new BezierCurve(a, b); var curveStartUp = math.rotate(a.Rotation, math.up()); var curveEndUp = math.rotate(b.Rotation, math.up()); return CurveUtility.EvaluateUpVector(curve, t, curveStartUp, curveEndUp); } return slice.GetCurveUpVector(knot.Knot, t); } /// /// Returns the interpolation ratio (0 to 1) that corresponds to a distance on a . The /// distance is relative to the curve. /// /// The zero-based index of the curve. /// The distance measured from the knot at curveIndex to convert to a normalized interpolation ratio. /// The normalized interpolation ratio that matches the distance on the designated curve. public float GetCurveInterpolation(int curveIndex, float curveDistance) { var knot = GetBranchKnotIndex(curveIndex); var slice = m_Splines[knot.Spline]; return slice.GetCurveInterpolation(knot.Knot, curveDistance); } } }