using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Collections; using Unity.Mathematics; namespace UnityEngine.Splines { /// /// A read-only representation of that is optimized for efficient access and queries. /// NativeSpline can be constructed with a spline and Transform. If a transform is applied, all values will be /// relative to the transformed knot positions. /// /// /// NativeSpline is compatible with the job system. /// public struct NativeSpline : ISpline, IDisposable { [ReadOnly] NativeArray m_Knots; [ReadOnly] NativeArray m_Curves; // As we cannot make a NativeArray of NativeArray all segments lookup tables are stored in a single array // each lookup table as a length of k_SegmentResolution and starts at index i = curveIndex * k_SegmentResolution [ReadOnly] NativeArray m_SegmentLengthsLookupTable; // As we cannot make a NativeArray of NativeArray all segments lookup tables are stored in a single array // each lookup table as a length of k_SegmentResolution and starts at index i = curveIndex * k_SegmentResolution [ReadOnly] NativeArray m_UpVectorsLookupTable; bool m_Closed; float m_Length; const int k_SegmentResolution = 30; /// /// A NativeArray of that form this Spline. /// /// /// Returns a reference to the knots array. /// public NativeArray Knots => m_Knots; /// /// A NativeArray of that form this Spline. /// /// /// Returns a reference to the curves array. /// public NativeArray Curves => m_Curves; /// /// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop). /// public bool Closed => m_Closed; /// /// Return the number of knots. /// public int Count => m_Knots.Length; /// /// Return the sum of all curve lengths, accounting for state. /// Note that this value is affected by the transform used to create this NativeSpline. /// /// /// Returns the sum length of all curves composing this spline, accounting for closed state. /// public float GetLength() => m_Length; /// /// Get the knot at . /// /// The zero-based index of the knot. public BezierKnot this[int index] => m_Knots[index]; /// /// Get an enumerator that iterates through the collection. /// /// An IEnumerator that is used to iterate the collection. public IEnumerator GetEnumerator() => m_Knots.GetEnumerator(); /// /// Gets an enumerator that iterates through the collection. /// /// An IEnumerator that is used to iterate the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Create a new NativeSpline from a set of . /// /// The object to convert to a . /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(ISpline spline, Allocator allocator = Allocator.Temp) : this(spline, float4x4.identity, false, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// The object to convert to a . /// Whether to cache the values of the Up vectors along the entire spline to reduce /// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might /// be less performant because all the Up vectors along the spline are computed. Consider how often you need to /// access the values of Up vectors along the spline before you cache them. /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(ISpline spline, bool cacheUpVectors, Allocator allocator = Allocator.Temp) : this(spline, float4x4.identity, cacheUpVectors, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// The object to convert to a . /// A transform matrix to be applied to the spline knots and tangents. /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(ISpline spline, float4x4 transform, Allocator allocator = Allocator.Temp) : this(spline, spline is IHasEmptyCurves disconnect ? disconnect.EmptyCurves : null, spline.Closed, transform, false, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// The object to convert to a . /// A transform matrix to be applied to the spline knots and tangents. /// Whether to cache the values of the Up vectors along the entire spline to reduce /// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might /// be less performant because all the Up vectors along the spline are computed. Consider how often you need to /// access the values of Up vectors along the spline before you cache them. /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(ISpline spline, float4x4 transform, bool cacheUpVectors, Allocator allocator = Allocator.Temp) : this(spline, spline is IHasEmptyCurves disconnect ? disconnect.EmptyCurves : null, spline.Closed, transform, cacheUpVectors, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// A collection of sequential forming the spline path. /// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop). /// Apply a transformation matrix to the control . /// The memory allocation method to use when reserving space for native arrays. public NativeSpline( IReadOnlyList knots, bool closed, float4x4 transform, Allocator allocator = Allocator.Temp) : this(knots, null, closed, transform, false, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// A collection of sequential forming the spline path. /// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop). /// Apply a transformation matrix to the control . /// Whether to cache the values of the Up vectors along the entire spline to reduce /// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might /// be less performant because all the Up vectors along the spline are computed. Consider how often you need to /// access the values of Up vectors along the spline before you cache them. /// The memory allocation method to use when reserving space for native arrays. public NativeSpline( IReadOnlyList knots, bool closed, float4x4 transform, bool cacheUpVectors, Allocator allocator = Allocator.Temp) : this(knots, null, closed, transform, cacheUpVectors, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// A collection of sequential forming the spline path. /// A collection of knot indices that should be considered degenerate curves for the /// purpose of creating a non-interpolated gap between curves. /// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop). /// Apply a transformation matrix to the control . /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(IReadOnlyList knots, IReadOnlyList splits, bool closed, float4x4 transform, Allocator allocator = Allocator.Temp) : this(knots, splits, closed, transform, false, allocator) { } /// /// Create a new NativeSpline from a set of . /// /// A collection of sequential forming the spline path. /// A collection of knot indices that should be considered degenerate curves for the /// purpose of creating a non-interpolated gap between curves. /// Whether the spline is open (has a start and end point) or closed (forms an unbroken loop). /// Apply a transformation matrix to the control . /// Whether to cache the values of the Up vectors along the entire spline to reduce /// the time it takes to access those Up vectors. If you set this to true, the creation of native splines might /// be less performant because all the Up vectors along the spline are computed. Consider how often you need to /// access the values of Up vectors along the spline before you cache them. /// The memory allocation method to use when reserving space for native arrays. public NativeSpline(IReadOnlyList knots, IReadOnlyList splits, bool closed, float4x4 transform, bool cacheUpVectors, Allocator allocator = Allocator.Temp) { int knotCount = knots.Count; m_Knots = new NativeArray(knotCount, allocator); m_Curves = new NativeArray(knotCount, allocator); m_SegmentLengthsLookupTable = new NativeArray(knotCount * k_SegmentResolution, allocator); m_Closed = closed; m_Length = 0f; //Costly to do this for temporary NativeSpline that does not require to access/compute up vectors m_UpVectorsLookupTable = new NativeArray(cacheUpVectors ? knotCount * k_SegmentResolution : 0, allocator); // As we cannot make a NativeArray of NativeArray all segments lookup tables are stored in a single array // each lookup table as a length of k_SegmentResolution and starts at index i = curveIndex * k_SegmentResolution var distanceToTimes = new NativeArray(k_SegmentResolution, Allocator.Temp); var upVectors = cacheUpVectors ? new NativeArray(k_SegmentResolution, Allocator.Temp) : default; if (knotCount > 0) { BezierKnot cur = knots[0].Transform(transform); for (int i = 0; i < knotCount; ++i) { BezierKnot next = knots[(i + 1) % knotCount].Transform(transform); m_Knots[i] = cur; if (splits != null && splits.Contains(i)) { m_Curves[i] = new BezierCurve(new BezierKnot(cur.Position), new BezierKnot(cur.Position)); var up = cacheUpVectors ? math.rotate(cur.Rotation, math.up()) : float3.zero; for (int n = 0; n < k_SegmentResolution; ++n) { //Cache Distance in case of a split is empty distanceToTimes[n] = new DistanceToInterpolation(); //up Vectors in case of a split is the knot up vector if(cacheUpVectors) upVectors[n] = up; } } else { m_Curves[i] = new BezierCurve(cur, next); CurveUtility.CalculateCurveLengths(m_Curves[i], distanceToTimes); if (cacheUpVectors) { var curveStartUp = math.rotate(cur.Rotation, math.up()); var curveEndUp = math.rotate(next.Rotation, math.up()); CurveUtility.EvaluateUpVectors(m_Curves[i], curveStartUp, curveEndUp, upVectors); } } if (m_Closed || i < knotCount - 1) m_Length += distanceToTimes[k_SegmentResolution - 1].Distance; for (int index = 0; index < k_SegmentResolution; index++) { m_SegmentLengthsLookupTable[i * k_SegmentResolution + index] = distanceToTimes[index]; if(cacheUpVectors) m_UpVectorsLookupTable[i * k_SegmentResolution + index] = upVectors[index]; } cur = next; } } } /// /// Get a from a knot index. /// /// The knot index that serves as the first control point for this curve. /// /// A formed by the knot at index and the next knot. /// public BezierCurve GetCurve(int index) => m_Curves[index]; /// /// Get the length of a . /// /// The 0 based index of the curve to find length for. /// The length of the bezier curve at index. public float GetCurveLength(int curveIndex) { return m_SegmentLengthsLookupTable[curveIndex * k_SegmentResolution + k_SegmentResolution - 1].Distance; } /// /// 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 spline. /// /// Returns the up vector at the t ratio of the curve of index 'index'. /// public float3 GetCurveUpVector(int index, float t) { // Value is not cached, compute the value directly on demand if (m_UpVectorsLookupTable.Length == 0) return this.CalculateUpVector(index, t); var curveIndex = index * k_SegmentResolution; var offset = 1f / (float)(k_SegmentResolution - 1); var curveT = 0f; for (int i = 0; i < k_SegmentResolution; i++) { if (t <= curveT + offset) { var value = math.lerp(m_UpVectorsLookupTable[curveIndex + i], m_UpVectorsLookupTable[curveIndex + i + 1], (t - curveT) / offset); return value; } curveT += offset; } //Otherwise, no value has been found, return the one at the end of the segment return m_UpVectorsLookupTable[curveIndex + k_SegmentResolution - 1]; } /// /// Release allocated resources. /// public void Dispose() { m_Knots.Dispose(); m_Curves.Dispose(); m_SegmentLengthsLookupTable.Dispose(); m_UpVectorsLookupTable.Dispose(); } // Wrapper around NativeSlice because the native type does not implement IReadOnlyList. struct Slice : IReadOnlyList where T : struct { NativeSlice m_Slice; public Slice(NativeArray array, int start, int count) { m_Slice = new NativeSlice(array, start, count); } public IEnumerator GetEnumerator() => m_Slice.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count => m_Slice.Length; public T this[int index] => m_Slice[index]; } /// /// Return the normalized interpolation (t) corresponding to a distance on a . /// /// The zero-based index of the curve. /// The curve-relative distance to convert to an interpolation ratio (also referred to as 't'). /// The normalized interpolation ratio associated to distance on the designated curve. public float GetCurveInterpolation(int curveIndex, float curveDistance) { if(curveIndex <0 || curveIndex >= m_SegmentLengthsLookupTable.Length || curveDistance <= 0) return 0f; var curveLength = GetCurveLength(curveIndex); if(curveDistance >= curveLength) return 1f; var startIndex = curveIndex * k_SegmentResolution; var slice = new Slice(m_SegmentLengthsLookupTable, startIndex, k_SegmentResolution); return CurveUtility.GetDistanceToInterpolation(slice, curveDistance); } } }