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