initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,43 @@
using System;
namespace UnityEditor.Splines
{
[Serializable]
struct CurveData
{
public static readonly CurveData empty = new CurveData
{
a = null,
b = null
};
public EditableKnot a { get; private set; }
public EditableKnot b { get; private set; }
public CurveData(EditableKnot firstKnot)
{
a = firstKnot;
//If first knot is last knot of the spline, use index 0 for the closing curve
var path = firstKnot.spline;
int nextIndex = firstKnot.index + 1;
if (nextIndex >= path.knotCount)
nextIndex = 0;
b = path.GetKnot(nextIndex);
}
public CurveData(EditableKnot firstKnot, EditableKnot lastKnot)
{
a = firstKnot;
b = lastKnot;
}
public CurveData(IEditableSpline spline, int firstIndex) : this(spline.GetKnot(firstIndex)){}
public bool IsValid()
{
return a != null && b != null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 92217b25452f8a94998d750fee5381d1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,120 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.Splines
{
[Serializable]
class EditableKnot : ISplineElement
{
internal static event Action<EditableKnot> knotModified;
[SerializeField]
float3 m_LocalPosition;
[SerializeField]
quaternion m_LocalRotation = quaternion.identity;
public IEditableSpline spline { get; internal set; }
public int index { get; internal set; }
public bool IsValid()
{
return index >= 0;
}
/// <summary> Matrix that transforms a point from local (knot) into world space. </summary>
public Matrix4x4 localToWorldMatrix => spline.localToWorldMatrix * Matrix4x4.TRS(localPosition, localRotation, Vector3.one);
/// <summary> Matrix that transforms a point from world space into local (knot) space. </summary>
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
public EditableKnot GetPrevious()
{
return spline.GetPreviousKnot(index, out EditableKnot previous) ? previous : null;
}
public EditableKnot GetNext()
{
return spline.GetNextKnot(index, out EditableKnot next) ? next : null;
}
/// <summary>
/// World space position of the knot.
/// </summary>
public float3 position
{
get => spline.localToWorldMatrix.MultiplyPoint3x4(localPosition);
set => localPosition = spline.worldToLocalMatrix.MultiplyPoint3x4(value);
}
/// <summary>
/// Local (spline space) position of the knot.
/// </summary>
public float3 localPosition
{
get => m_LocalPosition;
set
{
if (m_LocalPosition.Equals(value))
return;
m_LocalPosition = value;
SetDirty();
}
}
/// <summary>
/// World space rotation of the knot.
/// </summary>
public quaternion rotation
{
get => spline.localToWorldMatrix.rotation * localRotation;
set => localRotation = math.mul(spline.worldToLocalMatrix.rotation, value);
}
/// <summary>
/// Local (spline space) rotation of the knot.
/// </summary>
public quaternion localRotation
{
get => m_LocalRotation;
set
{
if (m_LocalRotation.Equals(value))
return;
m_LocalRotation = math.normalize(value);
SetDirty();
}
}
/// <summary>
/// How many editable tangents a knot contains. Cubic bezier splines contain 2 tangents, except at the ends of
/// a Spline that is not closed, in which case the knot contains a single tangent. Other spline type representations
/// may contain more or fewer tangents (ex, a Catmull-Rom spline does not expose any editable tangents).
/// </summary>
public int tangentCount => spline.tangentsPerKnot;
public virtual void Copy(EditableKnot other)
{
spline = other.spline;
index = other.index;
m_LocalPosition = other.localPosition;
m_LocalRotation = other.localRotation;
for (int i = 0, count = math.min(tangentCount, other.tangentCount); i < count; ++i)
GetTangent(i).Copy(other.GetTangent(i));
}
public void SetDirty()
{
knotModified?.Invoke(this);
spline?.SetDirty();
}
internal virtual EditableTangent GetTangent(int index) { return null; }
public virtual void ValidateData() {}
public virtual void OnPathUpdatedFromTarget() {}
public virtual void OnKnotInsertedOnCurve(EditableKnot previous, EditableKnot next, float t) {}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9eab45bfb720d641b442af1b8ad2543
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using UObject = UnityEngine.Object;
namespace UnityEditor.Splines
{
interface IEditableSpline
{
bool canBeClosed { get; set; }
bool closed { get; set; }
int knotCount { get; }
/// <summary>
/// How many editable tangents a knot contains. Cubic bezier splines contain 2 tangents, except at the ends of
/// a Spline that is not closed, in which case the knot contains a single tangent. Other spline type representations
/// may contain more or fewer tangents (ex, a Catmull-Rom spline does not expose any editable tangents).
/// </summary>
int tangentsPerKnot { get; }
/// <summary> Matrix that transforms a point from local (spline) into world space. </summary>
Matrix4x4 localToWorldMatrix { get; }
/// <summary> Matrix that transforms a point from world space into local (spline) space. </summary>
Matrix4x4 worldToLocalMatrix { get; }
EditableKnot GetKnot(int index);
bool GetPreviousKnot(int index, out EditableKnot knot);
bool GetNextKnot(int index, out EditableKnot knot);
void Resize(int targetKnotCount);
EditableKnot AddKnot();
void RemoveKnotAt(int index);
EditableKnot InsertKnot(int index);
CurveData GetPreviewCurveForEndKnot(float3 point, float3 normal, float3 tangentOut);
void OnKnotAddedAtEnd(EditableKnot knot, float3 normal, float3 tangentOut);
float3 GetPointOnCurve(CurveData curve, float t);
/// <summary>
/// Given an editable knot, returns its in and out tangents in local (spline) space.
/// </summary>
/// <param name="knot">An editable knot.</param>
/// <param name="localTangentIn">Knot's in tangent in local (spline) space.</param>
/// <param name="localTangentOut">Knot's out tangent in local (spline) space.</param>
void GetLocalTangents(EditableKnot knot, out float3 localTangentIn, out float3 localTangentOut);
void SetDirty();
void ToBezier(List<BezierKnot> results);
void FromBezier(IReadOnlyList<BezierKnot> knots);
bool isDirty { get; set; }
UObject conversionTarget { get; set; }
int conversionIndex { get; set; }
void ValidateData();
void CopyConversionDataFrom(IEditableSpline spline);
}
[Serializable]
abstract class EditableSpline<T> : IEditableSpline
where T : EditableKnot, new()
{
const int k_MinimumKnotCountToBeClosed = 3;
//Serialized fields will be used for tools inspector
[SerializeField]
List<T> m_Knots = new List<T>();
[SerializeField]
bool m_Closed = false;
UObject m_ConversionTarget;
int m_ConversionIndex;
bool m_IsDirty = false;
bool m_CanBeClosed = true;
protected EditableKnot m_PreviewKnotA;
protected EditableKnot m_PreviewKnotB;
public Matrix4x4 localToWorldMatrix =>
m_ConversionTarget != null && m_ConversionTarget is Component component
? component.transform.localToWorldMatrix
: Matrix4x4.identity;
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
UObject IEditableSpline.conversionTarget
{
get => m_ConversionTarget;
set => m_ConversionTarget = value;
}
//the index in the target array created at conversion
int IEditableSpline.conversionIndex
{
get => m_ConversionIndex;
set => m_ConversionIndex = value;
}
void IEditableSpline.CopyConversionDataFrom(IEditableSpline spline)
{
m_ConversionTarget = spline.conversionTarget;
m_ConversionIndex = spline.conversionIndex;
}
void IEditableSpline.ValidateData()
{
UpdateKnotIndices();
foreach (var knot in m_Knots)
{
knot.spline = this;
knot.ValidateData();
}
}
public bool canBeClosed
{
get => m_CanBeClosed;
set
{
m_CanBeClosed = value;
if (!m_CanBeClosed)
{
m_Closed = false;
SetDirty();
}
}
}
public bool closed
{
get => knotCount >= k_MinimumKnotCountToBeClosed && m_Closed;
set
{
if (m_Closed == value || !m_CanBeClosed)
return;
m_Closed = value;
SetDirty();
}
}
bool IEditableSpline.isDirty
{
get => m_IsDirty;
set => m_IsDirty = value;
}
public int knotCount => m_Knots.Count;
public virtual int tangentsPerKnot => 0;
EditableKnot IEditableSpline.GetKnot(int index)
{
return GetKnot(index);
}
public T GetKnot(int index)
{
return m_Knots[index];
}
public bool GetPreviousKnot(int index, out EditableKnot knot)
{
bool result = GetPreviousKnot(index, out T rawKnot);
knot = rawKnot;
return result;
}
public bool GetPreviousKnot(int index, out T knot)
{
if (knotCount > 0)
{
int next = index - 1;
if (next >= 0)
{
knot = m_Knots[next];
return true;
}
if (closed)
{
knot = m_Knots[m_Knots.Count - 1];
return true;
}
}
knot = null;
return false;
}
public bool GetNextKnot(int index, out EditableKnot knot)
{
if (knotCount > 0)
{
int next = index + 1;
if (next < m_Knots.Count)
{
knot = m_Knots[next];
return true;
}
if (closed)
{
knot = m_Knots[0];
return true;
}
}
knot = null;
return false;
}
public void Resize(int targetKnotCount)
{
if (knotCount > targetKnotCount)
{
m_Knots.RemoveRange(targetKnotCount, knotCount - targetKnotCount);
}
else if (knotCount < targetKnotCount)
{
while (knotCount < targetKnotCount)
{
AddKnot();
}
}
SetDirty();
}
public EditableKnot AddKnot()
{
var knot = CreateKnot();
knot.index = m_Knots.Count;
m_Knots.Add(knot);
SetDirty();
return knot;
}
public void RemoveKnotAt(int index)
{
EditableKnot knot = m_Knots[index];
SplineSelection.Remove(knot);
SplineSelection.OnKnotRemoved(this, index);
knot.index = -1;
m_Knots.RemoveAt(index);
UpdateKnotIndices();
SetDirty();
}
public EditableKnot InsertKnot(int index)
{
var knot = CreateKnot();
m_Knots.Insert(index, knot);
UpdateKnotIndices();
SetDirty();
SplineSelection.MoveAllIndexUpFromIndexToEnd(this, index);
return knot;
}
protected void CreatePreviewKnotsIfNeeded()
{
if (m_PreviewKnotA == null)
m_PreviewKnotA = CreateKnot();
if (m_PreviewKnotB == null)
m_PreviewKnotB = CreateKnot();
}
void UpdateKnotIndices()
{
for (int i = 0; i < m_Knots.Count; ++i)
{
m_Knots[i].index = i;
}
}
T CreateKnot()
{
return new T { spline = this };
}
public void SetDirty()
{
m_IsDirty = true;
}
public virtual void OnKnotAddedAtEnd(EditableKnot knot, float3 normal, float3 tangentOut) {}
public abstract float3 GetPointOnCurve(CurveData curve, float t);
public abstract void GetLocalTangents(EditableKnot knot, out float3 localTangentIn, out float3 localTangentOut);
public abstract CurveData GetPreviewCurveForEndKnot(float3 point, float3 normal, float3 tangentOut);
public abstract void ToBezier(List<BezierKnot> results);
public abstract void FromBezier(IReadOnlyList<BezierKnot> knots);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e98edb19c8e6294293225f04c7d4a67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine.Splines;
using UObject = UnityEngine.Object;
namespace UnityEditor.Splines
{
[InitializeOnLoad]
static class EditableSplineManager
{
// ONLY FOR TESTS. Used to add a path to the manager without requiring all the loops to get to it.
[EditorBrowsable(EditorBrowsableState.Never)]
internal sealed class TestManagedSpline : IDisposable
{
readonly UObject m_Target;
public TestManagedSpline(UObject target, IEditableSpline spline)
{
m_Target = target;
spline.conversionTarget = m_Target;
if (!s_Splines.TryGetValue(target, out TargetData data))
{
data = new TargetData();
s_Splines.Add(target, data);
}
data.RawSplines.Clear();
data.EditableSplines = new[] { spline };
}
public void Dispose()
{
if (m_Target != null)
s_Splines.Remove(m_Target);
}
}
internal sealed class TargetData
{
public readonly List<Spline> RawSplines = new List<Spline>();
public IEditableSpline[] EditableSplines = new IEditableSpline[0];
}
static readonly List<UObject> s_OwnersBuffer = new List<UObject>();
static readonly Dictionary<UObject, TargetData> s_Splines = new Dictionary<UObject, TargetData>();
static readonly Dictionary<UObject, TargetData> s_SplinesBackup = new Dictionary<UObject, TargetData>();
static EditableSplineManager()
{
AssemblyReloadEvents.beforeAssemblyReload += OnWillDomainReload;
Selection.selectionChanged += OnSelectionChanged;
Undo.undoRedoPerformed += OnUndoRedoPerformed;
OnSelectionChanged();
}
static void OnWillDomainReload()
{
FreeEntireCache();
}
static void OnSelectionChanged()
{
UObject[] selection = Selection.GetFiltered(typeof(ISplineProvider), SelectionMode.Editable);
UpdateSelection(selection);
}
static void OnUndoRedoPerformed()
{
FreeEntireCache();
}
internal static bool TryGetTargetData(UObject target, out TargetData targetData)
{
return s_Splines.TryGetValue(target, out targetData);
}
public static IReadOnlyList<IEditableSpline> GetEditableSplines(UObject target, bool createIfNotCached = true)
{
if (target == null)
return null;
if (!s_Splines.TryGetValue(target, out TargetData result))
{
if (!createIfNotCached)
return null;
var splineProvider = target as ISplineProvider;
if (splineProvider == null)
return null;
TargetData data = new TargetData();
var targetSplines = splineProvider.Splines;
if (targetSplines != null)
data.RawSplines.AddRange(targetSplines);
s_Splines.Add(target, data);
result = data;
SplineConversionUtility.UpdateEditableSplinesForTarget(target);
}
return result.EditableSplines;
}
public static void GetTargetsOfType(Type type, List<UObject> results)
{
ValidatePathOwners();
results.Clear();
foreach (var target in s_Splines.Keys)
{
if (target != null && type.IsInstanceOfType(target))
{
results.Add(target);
}
}
}
public static void EnsureCacheForTarget(UObject target)
{
GetEditableSplines(target);
}
public static void UpdateSelection(IEnumerable<UObject> selected)
{
if (selected == null)
return;
//Copy to backup
s_SplinesBackup.Clear();
foreach (var keyValuePair in s_Splines)
{
s_SplinesBackup.Add(keyValuePair.Key, keyValuePair.Value);
}
//Copy all that are still selected to real dictionary and ensure cache for newly selected
s_Splines.Clear();
foreach (var target in selected)
{
if (target != null)
{
if (s_SplinesBackup.TryGetValue(target, out TargetData data))
{
s_Splines.Add(target, data);
s_SplinesBackup.Remove(target);
}
else
{
EnsureCacheForTarget(target);
}
}
}
}
public static void FreeEntireCache()
{
s_Splines.Clear();
}
public static void FreeCacheForTarget(UObject target)
{
if (s_Splines.TryGetValue(target, out TargetData data))
{
s_Splines.Remove(target);
}
}
static void ValidatePathOwners()
{
s_OwnersBuffer.Clear();
foreach (var data in s_Splines)
{
// A dictionary key will never be fully null but the object could be destroyed
if (data.Key == null)
s_OwnersBuffer.Add(data.Key);
}
foreach (var o in s_OwnersBuffer)
{
s_Splines.Remove(o);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09cdc9b3ee4c5964082e9f6bae28aab6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,72 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.Splines
{
[Serializable]
sealed class EditableTangent : ISplineElement
{
internal event Action directionChanged;
[SerializeField]
float3 m_LocalPosition;
/// <summary> Local (knot space) position of the tangent. </summary>
public float3 localPosition
{
get => m_LocalPosition;
set
{
if (m_LocalPosition.Equals(value))
return;
m_LocalPosition = value;
directionChanged?.Invoke();
}
}
/// <summary> World space direction of the tangent. </summary>
public float3 direction
{
get => owner.localToWorldMatrix.MultiplyVector(localPosition);
set => localPosition = owner.worldToLocalMatrix.MultiplyVector(value);
}
/// <summary> World space position of the tangent. </summary>
public float3 position
{
get => owner.localToWorldMatrix.MultiplyPoint3x4(localPosition);
set => localPosition = owner.worldToLocalMatrix.MultiplyPoint3x4(value);
}
internal void SetLocalPositionNoNotify(float3 localPosition)
{
m_LocalPosition = localPosition;
}
public int tangentIndex { get; private set; }
public EditableKnot owner { get; private set; }
/// <summary> Matrix that transforms a point from local (tangent) into world space. </summary>
public Matrix4x4 localToWorldMatrix => owner.localToWorldMatrix *
Matrix4x4.TRS(localPosition, quaternion.identity, Vector3.one);
/// <summary> Matrix that transforms a point from world space into local (tangent) space. </summary>
public Matrix4x4 worldToLocalMatrix => localToWorldMatrix.inverse;
public EditableTangent() : this(null, -1) {}
public EditableTangent(EditableKnot owner, int tangentIndex)
{
this.owner = owner;
this.tangentIndex = tangentIndex;
}
public void Copy(EditableTangent other)
{
tangentIndex = other.tangentIndex;
m_LocalPosition = other.localPosition;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5cfd6cf4523fe044831ed4f356a5472
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
using UnityEditor.SettingsManagement;
using UnityEngine;
namespace UnityEditor.Splines
{
sealed class PathSettings
{
static Settings s_SettingsInstance;
public static Settings instance
{
get
{
if (s_SettingsInstance == null)
s_SettingsInstance = new Settings(new [] { new UserSettingsRepository() });
return s_SettingsInstance;
}
}
// Register a new SettingsProvider that will scrape the owning assembly for [UserSetting] marked fields.
[SettingsProvider]
static SettingsProvider CreateSettingsProvider()
{
var provider = new UserSettingsProvider("Preferences/Splines",
instance,
new[] { typeof(PathSettings).Assembly });
return provider;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0834e0204621424449fe7f88d7127f07
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;
namespace UnityEditor.Splines
{
[Serializable]
struct SelectableSplineElement : IEquatable<SelectableSplineElement>, IEquatable<EditableKnot>, IEquatable<EditableTangent>
{
public Object target;
public int pathIndex;
public int knotIndex;
public int tangentIndex; //-1 if knot
public SelectableSplineElement(ISplineElement element)
{
target = default;
pathIndex = -1;
knotIndex = -1;
tangentIndex = -1;
EditableKnot knot = null;
if (element is EditableKnot knotElement)
knot = knotElement;
else if (element is EditableTangent tangent)
{
knot = tangent.owner;
tangentIndex = tangent.tangentIndex;
}
if (knot != null)
{
target = knot.spline.conversionTarget;
pathIndex = knot.spline.conversionIndex;
knotIndex = knot.index;
}
}
public bool isTangent => tangentIndex >= 0;
public bool isKnot => tangentIndex < 0;
public bool Equals(EditableKnot other)
{
return IsTargetedKnot(other) && tangentIndex < 0;
}
public bool Equals(EditableTangent other)
{
return other != null && IsTargetedKnot(other.owner) && tangentIndex == other.tangentIndex;
}
public bool IsFromPath(IEditableSpline spline)
{
var pathInternal = spline;
return pathInternal.conversionTarget == target && pathInternal.conversionIndex == pathIndex;
}
bool IsTargetedKnot(EditableKnot knot)
{
if (knot == null)
return false;
return knotIndex == knot.index
&& pathIndex == knot.spline.conversionIndex
&& target == knot.spline.conversionTarget;
}
public bool Equals(SelectableSplineElement other)
{
return target == other.target && pathIndex == other.pathIndex && knotIndex == other.knotIndex && tangentIndex == other.tangentIndex;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is SelectableSplineElement other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (target != null ? target.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ pathIndex;
hashCode = (hashCode * 397) ^ knotIndex;
hashCode = (hashCode * 397) ^ tangentIndex;
return hashCode;
}
}
}
sealed class SelectionContext : ScriptableObject
{
static SelectionContext s_Instance;
public List<SelectableSplineElement> selection = new List<SelectableSplineElement>();
public int version;
public static SelectionContext instance
{
get
{
if (s_Instance == null)
{
s_Instance = CreateInstance<SelectionContext>();
s_Instance.hideFlags = HideFlags.HideAndDontSave;
}
return s_Instance;
}
}
SelectionContext()
{
if (s_Instance == null)
s_Instance = this;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33dac614537a60e43ac2b6692f9dc39a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,476 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using Object = UnityEngine.Object;
namespace UnityEditor.Splines
{
interface ISplineElement
{
float3 position { get; set; }
float3 localPosition { get; set; }
}
static class SplineSelection
{
public static event Action changed;
static readonly List<SelectableSplineElement> s_ElementBuffer = new List<SelectableSplineElement>();
static HashSet<Object> s_ObjectBuffer = new HashSet<Object>();
static SelectionContext context => SelectionContext.instance;
static List<SelectableSplineElement> selection => context.selection;
static int s_SelectionVersion;
static SplineSelection()
{
context.version = 0;
Undo.undoRedoPerformed += OnUndoRedoPerformed;
}
static void OnUndoRedoPerformed()
{
if (context.version != s_SelectionVersion)
{
s_SelectionVersion = context.version;
NotifySelectionChanged();
}
}
public static void Clear()
{
if (selection.Count == 0)
return;
IncrementVersion();
ClearNoUndo(true);
}
internal static void ClearNoUndo(bool notify)
{
selection.Clear();
if (notify)
NotifySelectionChanged();
}
static bool GetKnotFromElement(SelectableSplineElement element, out EditableKnot knot)
{
var paths = EditableSplineManager.GetEditableSplines(element.target, false);
if (paths == null || element.pathIndex >= paths.Count)
{
knot = null;
return false;
}
var path = paths[element.pathIndex];
if (element.knotIndex < 0 || element.knotIndex >= path.knotCount)
{
knot = null;
return false;
}
knot = path.GetKnot(element.knotIndex);
return true;
}
static bool GetTangentFromElement(SelectableSplineElement element, out EditableTangent tangent)
{
if (!GetKnotFromElement(element, out EditableKnot knot))
{
tangent = null;
return false;
}
tangent = knot.GetTangent(element.tangentIndex);
return tangent != null;
}
public static void GetSelectedKnots(List<EditableKnot> knots)
{
knots.Clear();
foreach (var element in selection)
if (element.isKnot && GetKnotFromElement(element, out EditableKnot knot))
knots.Add(knot);
}
public static void GetSelectedKnots(IEnumerable<Object> targets, List<EditableKnot> knots)
{
knots.Clear();
GetSelectedElementsInternal(targets, s_ElementBuffer);
foreach (var element in s_ElementBuffer)
if (element.isKnot && GetKnotFromElement(element, out EditableKnot knot))
knots.Add(knot);
}
public static void GetSelectedTangents(List<EditableTangent> tangents)
{
tangents.Clear();
foreach (var element in selection)
if (element.isTangent && GetTangentFromElement(element, out EditableTangent tangent))
tangents.Add(tangent);
}
public static void GetSelectedTangents(IEnumerable<Object> targets, List<EditableTangent> tangents)
{
tangents.Clear();
GetSelectedElementsInternal(targets, s_ElementBuffer);
foreach (var element in s_ElementBuffer)
if (element.isTangent && GetTangentFromElement(element, out EditableTangent tangent))
tangents.Add(tangent);
}
public static int count => selection.Count;
static ISplineElement ToSplineElement(SelectableSplineElement rawElement)
{
if (rawElement.isKnot)
{
if (GetKnotFromElement(rawElement, out EditableKnot knot))
return knot;
}
else if (rawElement.isTangent)
{
if (GetTangentFromElement(rawElement, out EditableTangent tangent))
return tangent;
}
return null;
}
public static ISplineElement GetActiveElement()
{
//Get first valid element
foreach (var rawElement in selection)
{
var element = ToSplineElement(rawElement);
if (element != null)
return element;
}
return null;
}
public static void GetSelectedElements(ICollection<ISplineElement> elements)
{
elements.Clear();
foreach (var rawElement in selection)
{
var element = ToSplineElement(rawElement);
if (element != null)
elements.Add(element);
}
}
public static void GetSelectedElements(IEnumerable<Object> targets, ICollection<ISplineElement> elements)
{
elements.Clear();
GetSelectedElementsInternal(targets, s_ElementBuffer);
foreach (var rawElement in s_ElementBuffer)
{
var element = ToSplineElement(rawElement);
if (element != null)
elements.Add(element);
}
}
static void GetSelectedElementsInternal(IEnumerable<Object> targets, List<SelectableSplineElement> results)
{
results.Clear();
foreach (var element in selection)
foreach(var target in targets)
{
if(target != null && element.target == target)
{
results.Add(element);
break;
}
}
}
public static bool IsActiveElement(ISplineElement element)
{
switch (element)
{
case EditableKnot knot: return IsActiveElement(knot);
case EditableTangent tangent: return IsActiveElement(tangent);
default: return false;
}
}
public static bool IsActiveElement(EditableKnot knot)
{
return IsActiveElement(new SelectableSplineElement(knot));
}
public static bool IsActiveElement(EditableTangent tangent)
{
return IsActiveElement(new SelectableSplineElement(tangent));
}
static bool IsActiveElement(SelectableSplineElement element)
{
return selection.Count > 0 && selection[0].Equals(element);
}
public static void SetActive(ISplineElement element)
{
switch (element)
{
case EditableKnot knot:
SetActive(knot);
break;
case EditableTangent tangent:
SetActive(tangent);
break;
}
}
public static void SetActive(EditableKnot knot)
{
SetActiveElement(new SelectableSplineElement(knot));
}
public static void SetActive(EditableTangent tangent)
{
SetActiveElement(new SelectableSplineElement(tangent));
}
static void SetActiveElement(SelectableSplineElement element)
{
int index = selection.IndexOf(element);
if (index == 0)
return;
IncrementVersion();
if (index > 0)
selection.RemoveAt(index);
selection.Insert(0, element);
if(element.target is Component component)
{
//Set the active unity object so the spline is the first target
Object[] unitySelection = Selection.objects;
var target = component.gameObject;
index = Array.IndexOf(unitySelection, target);
if(index > 0)
{
Object prevObj = unitySelection[0];
unitySelection[0] = unitySelection[index];
unitySelection[index] = prevObj;
Selection.objects = unitySelection;
}
}
NotifySelectionChanged();
}
public static void Add(ISplineElement element)
{
switch (element)
{
case EditableKnot knot:
Add(knot);
break;
case EditableTangent tangent:
Add(tangent);
break;
}
}
public static void Add(IEnumerable<ISplineElement> elements)
{
IncrementVersion();
bool changed = false;
foreach (var element in elements)
changed |= AddElement(new SelectableSplineElement(element));
if (changed)
NotifySelectionChanged();
}
public static void Add(EditableKnot knot)
{
IncrementVersion();
if (AddElement(new SelectableSplineElement(knot)))
NotifySelectionChanged();
}
public static void Add(IEnumerable<EditableKnot> knots)
{
IncrementVersion();
bool changed = false;
foreach (var knot in knots)
changed |= AddElement(new SelectableSplineElement(knot));
if (changed)
NotifySelectionChanged();
}
public static void Add(EditableTangent tangent)
{
IncrementVersion();
if (AddElement(new SelectableSplineElement(tangent)))
NotifySelectionChanged();
}
public static void Add(IEnumerable<EditableTangent> tangents)
{
IncrementVersion();
bool changed = false;
foreach (var tangent in tangents)
changed |= AddElement(new SelectableSplineElement(tangent));
if (changed)
NotifySelectionChanged();
}
static bool AddElement(SelectableSplineElement element)
{
if (!selection.Contains(element))
{
selection.Insert(0,element);
return true;
}
return false;
}
public static bool Remove(ISplineElement element)
{
switch (element)
{
case EditableKnot knot: return Remove(knot);
case EditableTangent tangent: return Remove(tangent);
default: return false;
}
}
public static bool Remove(EditableKnot knot)
{
IncrementVersion();
return RemoveElement(new SelectableSplineElement(knot));
}
public static bool Remove(EditableTangent tangent)
{
IncrementVersion();
return RemoveElement(new SelectableSplineElement(tangent));
}
static bool RemoveElement(SelectableSplineElement element)
{
if (selection.Remove(element))
{
NotifySelectionChanged();
return true;
}
return false;
}
public static bool Contains(ISplineElement element)
{
switch (element)
{
case EditableKnot knot: return Contains(knot);
case EditableTangent tangent: return Contains(tangent);
default: return false;
}
}
public static bool Contains(EditableKnot knot)
{
return ContainsElement(new SelectableSplineElement(knot));
}
public static bool Contains(EditableTangent tangent)
{
return ContainsElement(new SelectableSplineElement(tangent));
}
static bool ContainsElement(SelectableSplineElement element)
{
return selection.Contains(element);
}
internal static void UpdateObjectSelection(IEnumerable<Object> targets)
{
s_ObjectBuffer.Clear();
foreach (var target in targets)
if (target != null)
s_ObjectBuffer.Add(target);
IncrementVersion();
if (selection.RemoveAll(ObjectRemovePredicate) > 0)
NotifySelectionChanged();
}
static bool ObjectRemovePredicate(SelectableSplineElement element)
{
return !s_ObjectBuffer.Contains(element.target);
}
//Used when inserting new elements in spline
internal static void MoveAllIndexUpFromIndexToEnd(IEditableSpline spline, int index)
{
for (var i = 0; i < selection.Count; ++i)
{
var knot = selection[i];
if (knot.IsFromPath(spline))
{
if (knot.knotIndex >= index)
++knot.knotIndex;
selection[i] = knot;
}
}
}
//Used when deleting an element in spline
internal static void OnKnotRemoved(IEditableSpline spline, int index)
{
for (var i = selection.Count - 1; i >= 0; --i)
{
var knot = selection[i];
if (knot.IsFromPath(spline))
{
if (knot.knotIndex == index)
selection.RemoveAt(i);
else if (knot.knotIndex >= index)
{
--knot.knotIndex;
selection[i] = knot;
}
}
}
}
static void IncrementVersion()
{
Undo.RecordObject(context, "Spline Selection Changed");
++s_SelectionVersion;
++context.version;
}
static void NotifySelectionChanged()
{
changed?.Invoke();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7529a2acb193d1946b9ce1ecb41dd4d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: