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