using System;
using System.Collections;
using System.Collections.Generic;
namespace UnityEngine.Splines
{
///
/// A collection of KnotLinks to track how spline knots are linked and the utilities to
/// update these links when splines are modified.
///
[Serializable]
public sealed class KnotLinkCollection
{
[Serializable]
sealed class KnotLink : IReadOnlyList
{
public SplineKnotIndex[] Knots;
public IEnumerator GetEnumerator() => ((IEnumerable)Knots).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Knots.GetEnumerator();
public int Count => Knots.Length;
public SplineKnotIndex this[int index] => Knots[index];
}
[SerializeField]
KnotLink[] m_KnotsLink = new KnotLink[0];
///
/// How many KnotLinks the collection contains.
///
public int Count => m_KnotsLink.Length;
KnotLink GetKnotLinksInternal(SplineKnotIndex index)
{
foreach (var knotLink in m_KnotsLink)
if (Array.IndexOf(knotLink.Knots, index) >= 0)
return knotLink;
return null;
}
///
/// Gets the knots linked to a specific knot.
///
/// The of the knot.
/// The output list of the knots linked to the specified knot if they exist or null if they do not exist.
/// Returns true if linked knots are found, false otherwise.
public bool TryGetKnotLinks(SplineKnotIndex knotIndex, out IReadOnlyList linkedKnots)
{
linkedKnots = GetKnotLinksInternal(knotIndex);
return linkedKnots != null;
}
///
/// Gets the knots linked to a specific knot.
///
/// The of the knot.
/// Returns a list of knots linked to the specified knot. The specified knot is also in the list.
public IReadOnlyList GetKnotLinks(SplineKnotIndex knotIndex)
{
if(TryGetKnotLinks(knotIndex, out var linkedKnots))
return linkedKnots;
return new KnotLink { Knots = new[] { knotIndex } };
}
///
/// Clears all the links in the collection.
///
public void Clear()
{
m_KnotsLink = new KnotLink[0];
}
///
/// Links two knots positions to each other. If you link knots that are already linked to other knots, then all of the knots link to each other.
///
/// The first knot to link.
/// The first knot to link.
public void Link(SplineKnotIndex knotA, SplineKnotIndex knotB)
{
if (knotA.Equals(knotB))
return;
var originalLink = GetKnotLinksInternal(knotA);
var targetLink = GetKnotLinksInternal(knotB);
// If the knot was already linked to other knots, merge both shared knot
if (originalLink != null && targetLink != null)
{
if (originalLink.Equals(targetLink))
return;
var indices = new SplineKnotIndex[originalLink.Knots.Length + targetLink.Knots.Length];
Array.Copy(originalLink.Knots, indices, originalLink.Knots.Length);
Array.Copy(targetLink.Knots, 0, indices, originalLink.Knots.Length, targetLink.Knots.Length);
originalLink.Knots = indices;
ArrayUtility.Remove(ref m_KnotsLink, targetLink);
}
else if (targetLink != null)
{
var indices = targetLink.Knots;
ArrayUtility.Add(ref indices, knotA);
targetLink.Knots = indices;
}
else if (originalLink != null)
{
var indices = originalLink.Knots;
ArrayUtility.Add(ref indices, knotB);
originalLink.Knots = indices;
}
else
{
var newShared = new KnotLink { Knots = new[] {knotA, knotB}};
ArrayUtility.Add(ref m_KnotsLink, newShared);
}
}
///
/// Unlinks a knot from the knots it is linked to. This method unlinks the knot specified, but does not unlink the other knots from each other.
///
/// The knot to unlink.
public void Unlink(SplineKnotIndex knot)
{
var shared = GetKnotLinksInternal(knot);
if (shared == null)
return;
var indices = shared.Knots;
ArrayUtility.Remove(ref indices, knot);
shared.Knots = indices;
// Remove shared knot if empty or alone
if (shared.Knots.Length < 2)
ArrayUtility.Remove(ref m_KnotsLink, shared);
}
///
/// Updates the KnotLinkCollection after a spline is removed.
///
/// The index of the removed spline.
public void SplineRemoved(int splineIndex)
{
List indicesToRemove = new List(1);
for (var index = m_KnotsLink.Length - 1; index >= 0; --index)
{
var sharedKnot = m_KnotsLink[index];
indicesToRemove.Clear();
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
if (sharedKnot.Knots[i].Spline == splineIndex)
indicesToRemove.Add(i);
// Remove shared knot if it will become empty
if (sharedKnot.Knots.Length - indicesToRemove.Count < 2)
ArrayUtility.RemoveAt(ref m_KnotsLink, index);
else
{
var indices = sharedKnot.Knots;
ArrayUtility.SortedRemoveAt(ref indices, indicesToRemove);
sharedKnot.Knots = indices;
}
// Decrement by one every knot of a spline higher than this one
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
{
var knotIndex = sharedKnot.Knots[i];
if (knotIndex.Spline > splineIndex)
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline - 1, knotIndex.Knot);
}
}
}
///
/// Updates the KnotLinkCollection indices after a spline index changes.
///
/// The previous index of that spline in the SplineContainer.
/// The new index of that spline in the SplineContainer.
public void SplineIndexChanged(int previousIndex, int newIndex)
{
for (var index = m_KnotsLink.Length - 1; index >= 0; --index)
{
var sharedKnot = m_KnotsLink[index];
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
{
var knotIndex = sharedKnot.Knots[i];
if (knotIndex.Spline == previousIndex)
sharedKnot.Knots[i] = new SplineKnotIndex(newIndex, knotIndex.Knot);
else if (knotIndex.Spline > previousIndex && knotIndex.Spline <= newIndex)
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline - 1, knotIndex.Knot);
else if (knotIndex.Spline < previousIndex && knotIndex.Spline >= newIndex)
sharedKnot.Knots[i] = new SplineKnotIndex(knotIndex.Spline + 1, knotIndex.Knot);
}
}
}
///
/// Updates the KnotLinkCollection indices after a knot index changes.
///
/// The index of the spline.
/// The previous index of the knot in the spline.
/// The new index of the knot in the spline.
public void KnotIndexChanged(int splineIndex, int previousKnotIndex, int newKnotIndex) => KnotIndexChanged(new SplineKnotIndex(splineIndex, previousKnotIndex),new SplineKnotIndex(splineIndex, newKnotIndex));
///
/// Updates the KnotLinkCollection indices after a knot index changes.
///
/// The previous SplineKnotIndex of the knot.
/// The new SplineKnotIndex of the knot.
public void KnotIndexChanged(SplineKnotIndex previousIndex, SplineKnotIndex newIndex)
{
if (previousIndex.Knot > newIndex.Knot)
previousIndex.Knot += 1;
else
newIndex.Knot += 1;
//Insert the knot to shift indices
KnotInserted(newIndex);
//Link the 2 knots together temporary to link the new knot to the same knots as previous knot
Link(previousIndex, newIndex);
//Now that links has been updated, remove the previous knot
KnotRemoved(previousIndex);
}
///
/// Updates the KnotLinkCollection indices after a knot has been removed.
///
/// The index of the spline.
/// The index of the removed knot in the spline.
public void KnotRemoved(int splineIndex, int knotIndex) => KnotRemoved(new SplineKnotIndex(splineIndex, knotIndex));
///
/// Updates the KnotLinkCollection indices after a knot has been removed.
///
/// The SplineKnotIndex of the removed knot.
public void KnotRemoved(SplineKnotIndex index)
{
Unlink(index);
ShiftKnotIndices(index, -1);
}
///
/// Updates the KnotLinkCollection indices after a knot has been inserted.
///
/// The index of the spline.
/// The index of the inserted knot in the spline.
public void KnotInserted(int splineIndex, int knotIndex) => KnotInserted(new SplineKnotIndex(splineIndex, knotIndex));
///
/// Updates the KnotLinkCollection indices after a knot has been inserted.
///
/// The SplineKnotIndex of the inserted knot.
public void KnotInserted(SplineKnotIndex index) => ShiftKnotIndices(index, 1);
///
/// Changes the indices of the KnotLinkCollection to ensure they are valid. This is mainly used when splines or
/// knots are inserted or removed from a .
///
/// The SplineKnotIndex of the knot.
/// The offset to apply on other knots.
public void ShiftKnotIndices(SplineKnotIndex index, int offset)
{
foreach (var sharedKnot in m_KnotsLink)
{
for (int i = 0; i < sharedKnot.Knots.Length; ++i)
{
var current = sharedKnot.Knots[i];
// Increment by one every knot of the same spline above or equal to the inserted knot
if (current.Spline == index.Spline
&& current.Knot >= index.Knot)
sharedKnot.Knots[i] = new SplineKnotIndex(current.Spline, current.Knot + offset);
}
}
}
}
}