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,128 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Collections;
using Unity.Mathematics;
using Object = UnityEngine.Object;
namespace UnityEditor.Splines
{
static class EditableSplineUtility
{
public static SplineType GetSplineType(IEditableSpline spline)
{
switch (spline)
{
case BezierEditableSpline _:
return SplineType.Bezier;
case CatmullRomEditableSpline _:
return SplineType.CatmullRom;
case LinearEditableSpline _:
return SplineType.Linear;
default:
throw new ArgumentException(nameof(spline));
}
}
internal static IEditableSpline CreatePathOfType(SplineType type)
{
switch (type)
{
case SplineType.Bezier: return new BezierEditableSpline();
case SplineType.CatmullRom: return new CatmullRomEditableSpline();
case SplineType.Linear: return new LinearEditableSpline();
default:
throw new InvalidEnumArgumentException(nameof(type));
}
}
public static void GetSelectedSplines(IEnumerable<Object> targets, List<IEditableSpline> results)
{
results.Clear();
foreach (var target in targets)
{
var splines = GetSelectedSpline(target);
if (splines == null)
continue;
results.AddRange(splines);
}
}
public static IReadOnlyList<IEditableSpline> GetSelectedSpline(Object target)
{
return EditableSplineManager.GetEditableSplines(target);
}
internal static Bounds GetBounds(IReadOnlyList<ISplineElement> elements,
bool useKnotPositionForTangents = false)
{
if (elements == null)
throw new ArgumentNullException(nameof(elements));
if (elements.Count == 0)
return new Bounds(Vector3.positiveInfinity, Vector3.zero);
var element = elements[0];
var position = (useKnotPositionForTangents && element is EditableTangent)?
((EditableTangent)element).owner.position :
element.position;
Bounds bounds = new Bounds(position, Vector3.zero);
for (int i = 1; i < elements.Count; ++i)
{
element = elements[i];
if(useKnotPositionForTangents && element is EditableTangent tangent)
bounds.Encapsulate(tangent.owner.position);
else
bounds.Encapsulate(element.position);
}
return bounds;
}
public static EditableKnot InsertKnotOnCurve(CurveData curve, Vector3 position, float t)
{
var path = curve.a.spline;
var prev = curve.a;
var next = curve.b;
EditableKnot knot = path.InsertKnot(next.index);
knot.position = position;
knot.OnKnotInsertedOnCurve(prev, next, t);
return knot;
}
public static void AddPointToEnd(IEditableSpline spline, Vector3 worldPosition, Vector3 normal, Vector3 tangentOut)
{
if (spline.closed)
throw new ArgumentException("Cannot add a point to the end of a closed spline", nameof(spline));
EditableKnot knot = spline.AddKnot();
knot.position = worldPosition;
spline.OnKnotAddedAtEnd(knot, normal, tangentOut);
}
public static void CloseSpline(IEditableSpline spline)
{
if (spline.knotCount <= 1 && !spline.canBeClosed)
return;
spline.closed = true;
}
internal static float3 ToSplineSpaceTangent(this EditableKnot knot, float3 knotSpaceTangent)
{
return math.rotate(knot.localRotation, knotSpaceTangent);
}
internal static float3 ToKnotSpaceTangent(this EditableKnot knot, float3 splineSpaceTangent)
{
return math.rotate(math.inverse(knot.localRotation), splineSpaceTangent);
}
}
}

View file

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

View file

@ -0,0 +1,50 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
/// <summary>
/// Editor utility functions for working with <see cref="Spline"/> and <see cref="SplineData{T}"/>.
/// </summary>
public static class EditorSplineUtility
{
/// <summary>
/// Invoked once per-frame if a spline property has been modified.
/// </summary>
public static event Action<Spline> afterSplineWasModified;
static EditorSplineUtility()
{
Spline.afterSplineWasModified += (spline) =>
{
afterSplineWasModified?.Invoke(spline);
};
}
/// <summary>
/// Use this function to register a callback that gets invoked
/// once per-frame if any <see cref="SplineData{T}"/> changes occur.
/// </summary>
/// <param name="action">The callback to register.</param>
/// <typeparam name="T">
/// The type parameter of <see cref="SplineData{T}"/>.
/// </typeparam>
public static void RegisterSplineDataChanged<T>(Action<SplineData<T>> action)
{
SplineData<T>.afterSplineDataWasModified += action;
}
/// <summary>
/// Use this function to unregister <see cref="SplineData{T}"/> change callback.
/// </summary>
/// <param name="action">The callback to unregister.</param>
/// <typeparam name="T">
/// The type parameter of <see cref="SplineData{T}"/>.
/// </typeparam>
public static void UnregisterSplineDataChanged<T>(Action<SplineData<T>> action)
{
SplineData<T>.afterSplineDataWasModified -= action;
}
}
}

View file

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

View file

@ -0,0 +1,19 @@
using UnityEngine;
using Unity.Mathematics;
namespace UnityEditor.Splines
{
static class MathUtility
{
public static float GetRollAroundAxis(quaternion q, float3 axis)
{
axis = math.normalize(axis);
var orthoAxis = math.cross(axis, axis.Equals(math.up()) ? math.right() : math.up());
var rotatedOrtho = math.rotate(q, orthoAxis);
var flattened = math.normalize(Vector3.ProjectOnPlane(rotatedOrtho, axis));
var angle = Mathf.Deg2Rad * Vector3.SignedAngle(orthoAxis, flattened, axis);
return angle;
}
}
}

View file

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

View file

@ -0,0 +1,31 @@
using UnityEngine;
namespace UnityEditor.Splines
{
static class PathIcons
{
public static GUIContent knotPlacementTool = EditorGUIUtility.TrIconContent(GetIcon("KnotPlacementTool"), "Knot Placement Tool");
public static GUIContent splineMoveTool = EditorGUIUtility.TrIconContent("MoveTool", "Spline Move Tool");
public static GUIContent splineRotateTool = EditorGUIUtility.TrIconContent("RotateTool", "Spline Rotate Tool");
public static GUIContent splineScaleTool = EditorGUIUtility.TrIconContent("ScaleTool", "Spline Scale Tool");
static Texture2D GetIcon(string name)
{
bool is2x = EditorGUIUtility.pixelsPerPoint > 1;
bool darkSkin = EditorGUIUtility.isProSkin;
string path = string.Format($"Icons/{(darkSkin ? "d_" : "")}{name}{(is2x ? "@2x" : "")}");
Texture2D texture = Resources.Load<Texture2D>(path);
if (texture != null)
return texture;
path = string.Format($"Icons/{(darkSkin ? "d_" : "")}{name}");
texture = Resources.Load<Texture2D>(path);
if (texture != null)
return texture;
path = string.Format($"Icons/{name}");
return Resources.Load<Texture2D>(path);
}
}
}

View file

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

View file

@ -0,0 +1,59 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class SplineCacheUtility
{
const int k_SegmentsCount = 32;
static Dictionary<Spline, Vector3[]> s_SplineCacheTable = new Dictionary<Spline, Vector3[]>();
[InitializeOnLoadMethod]
static void Initialize()
{
EditorSplineUtility.afterSplineWasModified += delegate(Spline spline)
{
if(s_SplineCacheTable.ContainsKey(spline))
s_SplineCacheTable[spline] = null;
};
Spline.afterSplineWasModified += (Spline s) => ClearCache();
Undo.undoRedoPerformed += ClearCache;
SplineConversionUtility.splinesUpdated += ClearCache;
}
static void ClearCache()
{
s_SplineCacheTable.Clear();
}
public static void GetCachedPositions(Spline spline, out Vector3[] positions)
{
if(!s_SplineCacheTable.ContainsKey(spline))
s_SplineCacheTable.Add(spline, null);
int count = spline.Closed ? spline.Count : spline.Count - 1;
if(s_SplineCacheTable[spline] == null)
{
s_SplineCacheTable[spline] = new Vector3[count * k_SegmentsCount];
CacheCurvePositionsTable(spline, count);
}
positions = s_SplineCacheTable[spline];
}
static void CacheCurvePositionsTable(Spline spline, int curveCount, int segments = k_SegmentsCount)
{
float inv = 1f / (segments - 1);
for(int i = 0; i < curveCount; ++i)
{
var curve = spline.GetCurve(i);
var startIndex = i * k_SegmentsCount;
for(int n = 0; n < segments; n++)
(s_SplineCacheTable[spline])[startIndex + n] = CurveUtility.EvaluatePosition(curve, n * inv);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9fd9a2b5315b4e498978bab7499f95ac
timeCreated: 1646151455

View file

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
using UObject = UnityEngine.Object;
namespace UnityEditor.Splines
{
static class SplineConversionUtility
{
internal static Action splinesUpdated;
internal static Action splineTypeChanged;
static readonly List<UObject> s_TargetsBuffer = new List<UObject>();
static readonly List<BezierKnot> s_KnotBuffer = new List<BezierKnot>();
public static void UpdateEditableSplinesForTargetType<T>() where T : UObject
{
UpdateEditableSplinesForTargetType(typeof(T));
}
public static void UpdateEditableSplinesForTargetType(Type type)
{
EditableSplineManager.GetTargetsOfType(type, s_TargetsBuffer);
UpdateEditableSplinesForTargets(s_TargetsBuffer);
}
public static void UpdateEditableSplinesForTargets(IEnumerable<UObject> target)
{
foreach (var owner in target)
{
UpdateEditableSplinesInternal(owner, true);
}
splinesUpdated?.Invoke();
}
public static void UpdateEditableSplinesForTarget(UObject target)
{
UpdateEditableSplinesInternal(target, false);
}
static void UpdateEditableSplinesInternal(UObject target, bool silent)
{
//Update IEditableSpline from its Spline target
if (!EditableSplineManager.TryGetTargetData(target, out EditableSplineManager.TargetData data))
return;
if (data.RawSplines.Count == 0)
return;
var provider = (ISplineProvider) target;
data.RawSplines.Clear();
data.RawSplines.AddRange(provider.Splines);
//Ensure same number of editable splines than spline contained on the target
Array.Resize(ref data.EditableSplines, data.RawSplines.Count);
bool typeChanged = false;
for (var i = 0; i < data.RawSplines.Count; ++i)
{
var spline = data.RawSplines[i];
var editableSpline = data.EditableSplines[i];
if (editableSpline == null || EditableSplineUtility.GetSplineType(editableSpline) != spline.EditType)
{
var newPath = EditableSplineUtility.CreatePathOfType(spline.EditType);
newPath.conversionIndex = i;
newPath.conversionTarget = target;
data.EditableSplines[i] = newPath;
editableSpline = newPath;
typeChanged = true;
}
editableSpline.closed = spline.Closed;
s_KnotBuffer.Clear();
for (int j = 0; j < spline.Count; ++j)
{
s_KnotBuffer.Add(spline[j]);
}
editableSpline.FromBezier(s_KnotBuffer);
//Remove dirty flags to not reapply back to target
editableSpline.isDirty = false;
editableSpline.ValidateData();
}
if (typeChanged)
splineTypeChanged?.Invoke();
if (!silent)
splinesUpdated?.Invoke();
}
public static bool ApplyEditableSplinesOfTypeIfDirty<T>() where T : UObject
{
return ApplyEditableSplinesOfTypeIfDirty(typeof(T));
}
public static bool ApplyEditableSplinesOfTypeIfDirty(Type type)
{
EditableSplineManager.GetTargetsOfType(type, s_TargetsBuffer);
return ApplyEditableSplinesIfDirty(s_TargetsBuffer);
}
public static bool ApplyEditableSplinesIfDirty(IEnumerable<UObject> owners)
{
bool result = false;
foreach (var owner in owners)
{
result |= ApplyEditableSplinesIfDirty(owner);
}
return result;
}
public static bool ApplyEditableSplinesIfDirty(UObject target)
{
if (!EditableSplineManager.TryGetTargetData(target, out EditableSplineManager.TargetData data))
return false;
if (!IsAnyEditableSplineDirty(data.EditableSplines))
return false;
Undo.RecordObject(target, "Apply Changes to Spline");
for (var i = 0; i < data.EditableSplines.Length; ++i)
{
var editablePath = data.EditableSplines[i];
var targetSpline = data.RawSplines[i];
if (editablePath.isDirty)
{
editablePath.isDirty = false;
targetSpline.Closed = editablePath.closed;
s_KnotBuffer.Clear();
editablePath.ToBezier(s_KnotBuffer);
targetSpline.Resize(s_KnotBuffer.Count);
for (int j = 0; j < s_KnotBuffer.Count; ++j)
{
targetSpline[j] = s_KnotBuffer[j];
}
}
}
return true;
}
static bool IsAnyEditableSplineDirty(IEnumerable<IEditableSpline> paths)
{
foreach (var path in paths)
{
if (path.isDirty)
return true;
}
return false;
}
}
}

View file

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

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Internal;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
[ExcludeFromDocs]
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class CustomSplineDataHandle : Attribute
{
internal Type m_Type;
// Tells a PropertyDrawer class which run-time class or attribute it's a drawer for.
public CustomSplineDataHandle(Type type)
{
m_Type = type;
}
}
interface ISplineDataHandle
{
public SplineDataHandleAttribute attribute
{ get; }
void SetAttribute(SplineDataHandleAttribute attribute);
}
/// <summary>
/// SplineDataHandle is a base class to override in order to enable custom handles for spline data.
/// The Drawer needs to inherit from this class and override the method corresponding to the correct splineData type.
/// Either one of the method or both can be overriden regarding the user needs.
/// </summary>
/// <typeparam name="T">
/// The type parameter of the <see cref="SplineData{T}"/> that this drawer targets.
/// </typeparam>
public abstract class SplineDataHandle<T> : ISplineDataHandle
{
internal int[] m_ControlIDs;
SplineDataHandleAttribute m_Attribute;
[ExcludeFromDocs]
public SplineDataHandleAttribute attribute => m_Attribute;
/// <summary>
/// Array of reserved control IDs used for <see cref="SplineData{T}"/> handles.
/// </summary>
public int[] controlIDs => m_ControlIDs;
void ISplineDataHandle.SetAttribute(SplineDataHandleAttribute attribute)
{
m_Attribute = attribute;
}
/// <summary>
/// Override this method to create custom handles for <see cref="SplineData{T}"/>,
/// this method is called before DrawKeyframe in the render loop.
/// </summary>
/// <param name="splineData">The <see cref="SplineData{T}"/> for which the method is drawing handles.</param>
/// <param name="spline">The target Spline associated to the SplineData for the drawing.</param>
/// <param name="localToWorld">The spline localToWorld Matrix.</param>
/// <param name="color">The color defined in the SplineData scene interface.</param>
public virtual void DrawSplineData(SplineData<T> splineData, Spline spline, Matrix4x4 localToWorld, Color color)
{}
/// <summary>
/// Override this method to create custom handles for a <see cref="DataPoint{T}"/>in <see cref="SplineData{T}"/>,
/// 'position' and 'direction' are given in the Spline-space basis.
/// This method is called after DrawSplineData in the render loop.
/// </summary>
/// <param name="controlID">A control ID from <see cref="controlIDs"/> that represents this handle.</param>
/// <param name="position">The position of the keyframe data in spline space.</param>
/// <param name="direction">The direction of the spline at the current keyframe.</param>
/// <param name="upDirection">The up vector orthogonal to the spline direction at the current keyframe regarding knot rotation.</param>
/// <param name="splineData">The <see cref="SplineData{T}"/> for which the method is drawing handles.</param>
/// <param name="dataPointIndex">The index of the current keyframe to handle.</param>
public virtual void DrawDataPoint(
int controlID,
Vector3 position,
Vector3 direction,
Vector3 upDirection,
SplineData<T> splineData,
int dataPointIndex)
{}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2495f958516e4867aa9cbcbcf916836c
timeCreated: 1625521293

View file

@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor.SettingsManagement;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
[InitializeOnLoad]
static class SplineDataHandlesManager //: ScriptableSingleton<SplineDataHandlesManager>
{
// Using by default colors of maximal contrast from
// https://medium.com/@rjurney/kellys-22-colours-of-maximum-contrast-58edb70c90d1
static UserSetting<Color> s_Color0 = new UserSetting<Color>(PathSettings.instance, "SplineData.color0", new Color32(0, 130, 200, 255), SettingsScope.User); // Blue
static UserSetting<Color> s_Color1 = new UserSetting<Color>(PathSettings.instance, "SplineData.color1", new Color32(245, 130, 48, 255), SettingsScope.User); // Orange
static UserSetting<Color> s_Color2 = new UserSetting<Color>(PathSettings.instance, "SplineData.color2", new Color32(60, 180, 75, 255), SettingsScope.User); // Green
static UserSetting<Color> s_Color3 = new UserSetting<Color>(PathSettings.instance, "SplineData.color3", new Color32(255, 225, 25, 255), SettingsScope.User); // Yellow
static UserSetting<Color> s_Color4 = new UserSetting<Color>(PathSettings.instance, "SplineData.color4", new Color32(145, 30, 180, 255), SettingsScope.User); // Purple
static UserSetting<Color> s_Color5 = new UserSetting<Color>(PathSettings.instance, "SplineData.color5", new Color32(70, 240, 240, 255), SettingsScope.User); // Cyan
static UserSetting<Color> s_Color6 = new UserSetting<Color>(PathSettings.instance, "SplineData.color6", new Color32(250, 190, 190, 255), SettingsScope.User); // Pink
static UserSetting<Color> s_Color7 = new UserSetting<Color>(PathSettings.instance, "SplineData.color7", new Color32(0, 128, 128, 255), SettingsScope.User); // Teal
static UserSetting<Color>[] s_DefaultSplineColors = null;
static UserSetting<Color>[] defaultSplineColors
{
get
{
if(s_DefaultSplineColors == null)
s_DefaultSplineColors = new UserSetting<Color>[]
{
s_Color0,
s_Color1,
s_Color2,
s_Color3,
s_Color4,
s_Color5,
s_Color6,
s_Color7
};
return s_DefaultSplineColors;
}
}
static Dictionary<Type, ISplineDataHandle> s_DrawerTypes = null;
static Dictionary<Type, ISplineDataHandle> drawerTypes
{
get
{
if(s_DrawerTypes == null)
InitializeDrawerTypes();
return s_DrawerTypes;
}
}
public static bool hasSplineDataIsSelection => s_SelectedSplineDataContainers.Count > 0;
static List<SplineDataContainer> s_SelectedSplineDataContainers = new List<SplineDataContainer>();
public static List<SplineDataContainer> selectedSplineDataContainers => s_SelectedSplineDataContainers;
public static Action onSplineDataSelectionChanged;
static List<Component> s_AllComponents = new List<Component>();
static List<Component> s_TmpComponents = new List<Component>();
internal class SplineDataContainer
{
public GameObject owner;
public List<SplineDataElement> splineDataElements = new List<SplineDataElement>();
}
internal class SplineDataElement
{
public FieldInfo splineDataField;
public MethodInfo drawMethodInfo;
public MethodInfo drawKeyframeMethodInfo;
public ISplineDataHandle customDrawerInstance;
public SplineDataHandleAttribute customDrawerAttribute;
public MethodInfo initCustomDrawMethodInfo;
public MethodInfo customSplineDataDrawHandleMethodInfo;
public MethodInfo customDataPointDrawHandleMethodInfo;
public SplineContainer splineContainer = null;
public Component component = null;
public bool displayed = true;
public UserSetting<Color> color;
public SplineDataHandlesDrawer.LabelType labelType = SplineDataHandlesDrawer.LabelType.None;
}
static SplineDataHandlesManager()
{
SceneView.duringSceneGui += OnSceneGUI;
Selection.selectionChanged += OnSelectionChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
static void OnPlayModeStateChanged(PlayModeStateChange playModeStateChange)
{
OnSelectionChanged();
}
static void OnSelectionChanged()
{
UpdateSplineDataElements();
}
static void InitializeDrawerTypes()
{
s_DrawerTypes = new Dictionary<Type, ISplineDataHandle>();
var drawerTypes = TypeCache.GetTypesDerivedFrom(typeof(SplineDataHandle<>));
foreach(var drawerType in drawerTypes)
{
var attributes = drawerType.GetCustomAttributes(typeof(CustomSplineDataHandle));
if(attributes.Any())
{
var drawerAttribute = (attributes.First() as CustomSplineDataHandle).m_Type;
var drawerInstance = drawerType.Assembly.CreateInstance(drawerType.ToString()) as ISplineDataHandle;
s_DrawerTypes.Add(drawerAttribute, drawerInstance);
}
}
}
static bool VerifyComponentsIntegrity()
{
var selection = Selection.gameObjects;
int componentCount = 0;
foreach(GameObject go in selection)
{
go.GetComponents(typeof(Component), s_TmpComponents);
foreach(var component in s_TmpComponents)
{
if(s_AllComponents.Contains(component))
componentCount++;
else //A new component has been added
return false;
}
}
//Check if no components has been removed
return componentCount == s_AllComponents.Count;
}
static void UpdateSplineDataElements()
{
if(Selection.activeGameObject == null)
{
if(s_SelectedSplineDataContainers.Count > 0)
{
s_SelectedSplineDataContainers.Clear();
onSplineDataSelectionChanged?.Invoke();
}
return;
}
s_SelectedSplineDataContainers.Clear();
var gameObjects = Selection.gameObjects;
SplineDataContainer container = null;
FieldInfo[] fieldInfos;
s_AllComponents.Clear();
int colorIndex = 0;
foreach(GameObject go in gameObjects)
{
container = null;
go.GetComponents(typeof(Component), s_TmpComponents);
s_AllComponents.AddRange(s_TmpComponents);
foreach(Component component in s_TmpComponents)
{
if(component == null)
continue;
fieldInfos = component.GetType().GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
for(int i = 0; i < fieldInfos.Length; i++)
{
var fieldType = fieldInfos[i].FieldType;
if(fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(SplineData<>))
{
SplineContainer splineContainer = go.GetComponent<SplineContainer>();
//If no spline container on the GameObject, check if the current Component is referencing one
if(splineContainer == null)
{
var fi = fieldInfos.First(field => field.FieldType == typeof(SplineContainer));
if(fi.FieldType == typeof(SplineContainer))
splineContainer = (SplineContainer)fi.GetValue(component);
}
//Skip this SplineData if no splineContainer can be automatically attributed
if(splineContainer == null)
continue;
Type splineDataType = fieldInfos[i].FieldType.GetTypeInfo().GenericTypeArguments[0];
Type drawer = typeof(SplineDataHandlesDrawer);
SplineDataHandleAttribute splineDataAttribute = fieldInfos[i].GetCustomAttribute<SplineDataHandleAttribute>();
ISplineDataHandle customDrawerInstance = null;
MethodInfo initCustomDrawMethodInfo = null;
MethodInfo customSplineDataHandleMethodInfo = null;
MethodInfo customKeyframeHandleMethodInfo = null;
if(splineDataAttribute != null)
{
if(drawerTypes.ContainsKey(splineDataAttribute.GetType()))
{
var splineDataHandleInstance = drawerTypes[splineDataAttribute.GetType()];
var splineDataHandleType = splineDataHandleInstance.GetType();
var drawerDataType = splineDataHandleType.BaseType?.GenericTypeArguments[0];
if(drawerDataType != splineDataType)
{
Debug.LogError($"The data type '{splineDataType}' used for field {fieldInfos[i].Name} in {component.GetType().Name} " +
$"does not correspond to the type '{drawerDataType}' used in {splineDataHandleType.Name}");
}
else
{
initCustomDrawMethodInfo =
drawer.GetMethod("InitCustomHandles",
BindingFlags.Static | BindingFlags.NonPublic
)?.MakeGenericMethod(splineDataType);
var customDrawerMethodInfos = splineDataHandleType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
customSplineDataHandleMethodInfo = GetCustomSplineDataDrawerMethodInfo(customDrawerMethodInfos, splineDataType);
customKeyframeHandleMethodInfo = GetCustomKeyframeDrawerMethodInfo(customDrawerMethodInfos, splineDataType);
customDrawerInstance = splineDataHandleInstance;
}
}
else
Debug.LogError($"No valid SplineDataHandle<> type was found for drawerID = \"{splineDataAttribute.GetType()}\" (used in {component.gameObject.name}/{component.GetType().Name})");
}
else
continue;
MethodInfo drawerMethod = drawer.GetMethod("DrawSplineDataHandles", BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo drawerMethodInfo = drawerMethod.MakeGenericMethod(splineDataType);
MethodInfo dpDrawerMethod = drawer.GetMethod("DrawCustomHandles", BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo dataPointDrawerMethodInfo = dpDrawerMethod.MakeGenericMethod(splineDataType);
var splineDataElement = new SplineDataElement()
{
splineDataField = fieldInfos[i],
drawMethodInfo = drawerMethodInfo,
drawKeyframeMethodInfo = dataPointDrawerMethodInfo,
splineContainer = splineContainer,
component = component,
customDrawerInstance = customDrawerInstance,
customDrawerAttribute = splineDataAttribute,
initCustomDrawMethodInfo = initCustomDrawMethodInfo,
customSplineDataDrawHandleMethodInfo = customSplineDataHandleMethodInfo,
customDataPointDrawHandleMethodInfo = customKeyframeHandleMethodInfo,
color = defaultSplineColors[colorIndex++ % defaultSplineColors.Length]
};
if(container == null)
{
container = new SplineDataContainer() { owner = go };
s_SelectedSplineDataContainers.Add(container);
}
container.splineDataElements.Add(splineDataElement);
}
}
}
}
onSplineDataSelectionChanged?.Invoke();
}
static MethodInfo GetCustomSplineDataDrawerMethodInfo(MethodInfo[] methodInfos, System.Type splineDataType)
{
foreach(var methodInfo in methodInfos)
{
//Checking if the method has the correct name
if(!methodInfo.Name.Equals("DrawSplineData"))
continue;
//Checking if the method as parameters
var parameters = methodInfo.GetParameters();
if(parameters.Length > 0)
{
//Is the first parameter with a generic type
var parameterTypeInfo = parameters[0].ParameterType.GetTypeInfo();
if(parameterTypeInfo.IsGenericType && parameterTypeInfo.GetGenericTypeDefinition() == typeof(SplineData<>))
{
var splineDataTargetType = parameterTypeInfo.GenericTypeArguments[0];
if(splineDataTargetType == splineDataType)
return methodInfo;
}
}
}
return null;
}
static MethodInfo GetCustomKeyframeDrawerMethodInfo(MethodInfo[] methodInfos, System.Type splineDataType)
{
foreach(var methodInfo in methodInfos)
{
//Checking if the method has the correct name
if(!methodInfo.Name.Equals("DrawDataPoint"))
continue;
//Checking if the method as parameters
var parameters = methodInfo.GetParameters();
if(parameters.Length == 6)
{
//Is the first parameter with a generic type
var parameterTypeInfo = parameters[4].ParameterType.GetTypeInfo();
if(parameterTypeInfo.IsGenericType && parameterTypeInfo.GetGenericTypeDefinition() == typeof(SplineData<>))
{
var splineDataTargetType = parameterTypeInfo.GenericTypeArguments[0];
if(splineDataTargetType == splineDataType)
return methodInfo;
}
}
}
return null;
}
static void OnSceneGUI(SceneView view)
{
if(!VerifyComponentsIntegrity())
OnSelectionChanged();
foreach(var sdContainer in s_SelectedSplineDataContainers)
{
foreach(var splineDataElement in sdContainer.splineDataElements)
{
if(!splineDataElement.displayed || splineDataElement.splineContainer == null)
continue;
var mono = splineDataElement.component as MonoBehaviour;
if((mono != null && !mono.isActiveAndEnabled) || !splineDataElement.splineContainer.isActiveAndEnabled)
continue;
splineDataElement.drawMethodInfo?.Invoke(null,
new object[]
{
splineDataElement.splineDataField.GetValue(splineDataElement.component),
splineDataElement.component,
splineDataElement.splineContainer.Spline,
splineDataElement.splineContainer.transform.localToWorldMatrix,
splineDataElement.color.value,
splineDataElement.labelType
});
if(splineDataElement.customDrawerInstance != null)
{
splineDataElement.customDrawerInstance.SetAttribute(splineDataElement.customDrawerAttribute);
var splineData = splineDataElement.splineDataField.GetValue(splineDataElement.component);
var spline = splineDataElement.splineContainer.Spline;
var localToWorld = splineDataElement.splineContainer.transform.localToWorldMatrix;
splineDataElement.initCustomDrawMethodInfo?.Invoke(null,
new object[]
{
splineData,
splineDataElement.customDrawerInstance
});
splineDataElement.drawKeyframeMethodInfo?.Invoke(null,
new object[]
{
splineData,
splineDataElement.component,
spline,
localToWorld,
splineDataElement.color.value,
splineDataElement.customDrawerInstance,
splineDataElement.customSplineDataDrawHandleMethodInfo,
splineDataElement.customDataPointDrawHandleMethodInfo
});
}
}
}
Handles.matrix = Matrix4x4.identity;
Handles.color = Color.white;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7432e9702633479db8cad31be8640525
timeCreated: 1635361594

View file

@ -0,0 +1,90 @@
using UnityEditor.SettingsManagement;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
/// <summary>
/// SplineGizmoUtility provides methods for drawing in-scene representations of Splines.
/// </summary>
public static class SplineGizmoUtility
{
[UserSetting]
internal static UserSetting<Color> s_GizmosLineColor = new UserSetting<Color>(PathSettings.instance, "Gizmos.SplineColor", Color.blue, SettingsScope.User);
[UserSettingBlock("Gizmos")]
static void GizmosColorPreferences(string searchContext)
{
s_GizmosLineColor.value = SettingsGUILayout.SettingsColorField("Splines Color", s_GizmosLineColor, searchContext);
}
static Vector3[] s_Points;
static Vector3 s_CameraUp;
static Color s_OutlineColor = new Color(0f, 0f, 0f, .5f);
/// <summary>
/// Draw a line gizmo for a <see cref="ISplineProvider"/>.
/// </summary>
/// <param name="provider">An object implementing the ISplineProvider interface. Usually this will be a MonoBehaviour.</param>
public static void DrawGizmos(ISplineProvider provider)
{
var splines = provider.Splines;
if (splines == null)
return;
s_CameraUp = SceneView.lastActiveSceneView.camera.transform.up;
var localToWorld = ((MonoBehaviour)provider).transform.localToWorldMatrix;
foreach(var spline in splines)
{
if(spline == null || spline.Count < 2)
continue;
Vector3[] positions;
SplineCacheUtility.GetCachedPositions(spline, out positions);
var color = Gizmos.color;
var from = localToWorld.MultiplyPoint(positions[0]);
var previousDir = Vector3.zero;
for(int i = 1; i < positions.Length; ++i)
{
var to = localToWorld.MultiplyPoint(positions[i]);
var center = ( from + to ) / 2f;
var size = .1f * HandleUtility.GetHandleSize(center);
var dir = to - from;
var delta = previousDir.magnitude == 0 ? 1f :Vector3.Dot(previousDir, dir.normalized);
//If the angle is too wide between 2 positions, take the previous position to draw the line
if(delta < 0.9f)
{
Gizmos.color = color;
DrawLineSegment(from, from + previousDir, size);
from = from + previousDir;
dir = to - from;
}
//Is the second position far enough to draw the segment
if(i == positions.Length-1 || dir.magnitude > size)
{
Gizmos.color = color;
DrawLineSegment(from, to, size);
from = to;
previousDir = Vector3.zero;
}
else
previousDir = dir;
}
Gizmos.matrix = Matrix4x4.identity;
}
}
static void DrawLineSegment(Vector3 from, Vector3 to, float size)
{
Gizmos.DrawLine(from, to);
Gizmos.color = s_OutlineColor;
//make the gizmo a little thicker
var offset = size * s_CameraUp / 7.5f;
Gizmos.DrawLine(from - offset, to - offset);
}
}
}

View file

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

View file

@ -0,0 +1,232 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;
namespace UnityEditor.Splines
{
struct ColorScope : IDisposable
{
readonly Color m_PrevColor;
public ColorScope(Color color)
{
m_PrevColor = Handles.color;
Handles.color = color;
}
public void Dispose()
{
Handles.color = m_PrevColor;
}
}
struct ZTestScope : IDisposable
{
readonly CompareFunction m_Original;
public ZTestScope(CompareFunction function)
{
m_Original = Handles.zTest;
Handles.zTest = function;
}
public void Dispose()
{
Handles.zTest = m_Original;
}
}
static class SplineHandleUtility
{
const int k_MaxDecimals = 15;
const int k_SegmentsPointCount = 30;
static readonly Vector3[] s_ClosestPointArray = new Vector3[k_SegmentsPointCount];
const float k_KnotPickingDistance = 18f;
static readonly Vector3[] s_LineBuffer = new Vector3[2];
internal static Ray TransformRay(Ray ray, Matrix4x4 matrix)
{
return new Ray(matrix.MultiplyPoint3x4(ray.origin), matrix.MultiplyVector(ray.direction));
}
internal static Vector3 DoIncrementSnap(Vector3 position, Vector3 previousPosition)
{
var delta = position - previousPosition;
var right = Tools.handleRotation * Vector3.right;
var up = Tools.handleRotation * Vector3.up;
var forward = Tools.handleRotation * Vector3.forward;
var snappedDelta =
Snapping.Snap(Vector3.Dot(delta, right), EditorSnapSettings.move[0]) * right +
Snapping.Snap(Vector3.Dot(delta, up), EditorSnapSettings.move[1]) * up +
Snapping.Snap(Vector3.Dot(delta, forward), EditorSnapSettings.move[2]) * forward;
return previousPosition + snappedDelta;
}
static Vector3 SnapToGrid(Vector3 position)
{
//todo Temporary version, waiting for a trunk PR to land to move to the commented version:
//#if UNITY_2022_2_OR_NEWER
// if(EditorSnapSettings.gridSnapActive)
// return Snapping.Snap(position, EditorSnapSettings.gridSize, SnapAxis.All);
//#else
GameObject tmp = new GameObject();
tmp.hideFlags = HideFlags.HideAndDontSave;
var trs = tmp.transform;
trs.position = position;
Handles.SnapToGrid(new []{trs});
var snapped = trs.position;
Object.DestroyImmediate(tmp);
return snapped;
//#endif
}
internal static bool GetPointOnSurfaces(Vector2 mousePosition, out Vector3 point, out Vector3 normal)
{
#if UNITY_2020_1_OR_NEWER
if(HandleUtility.PlaceObject(mousePosition, out point, out normal))
{
if(EditorSnapSettings.gridSnapEnabled)
point = SnapToGrid(point);
return true;
}
#endif
var ray = HandleUtility.GUIPointToWorldRay(mousePosition);
#if !UNITY_2020_1_OR_NEWER
if (Physics.Raycast(ray, out RaycastHit hit))
{
point = hit.point;
normal = hit.normal;
return true;
}
#endif
//Backup if couldn't find a surface
var constraint = new Plane(Vector3.up, Vector3.zero); //This should be in the direction of the current grid
if (constraint.Raycast(ray, out float distance))
{
normal = constraint.normal;
point = ray.origin + ray.direction * distance;
if(EditorSnapSettings.gridSnapEnabled)
point = SnapToGrid(point);
return true;
}
point = normal = Vector3.zero;
return false;
}
internal static void DrawLineWithWidth(Vector3 a, Vector3 b, float width, Texture2D lineAATex = null)
{
s_LineBuffer[0] = a;
s_LineBuffer[1] = b;
Handles.DrawAAPolyLine(lineAATex, width, s_LineBuffer);
}
public static float DistanceToKnot(Vector3 position)
{
return DistanceToCircle(position, k_KnotPickingDistance);
}
public static float DistanceToCircle(Vector3 point, float radius)
{
Vector3 screenPos = HandleUtility.WorldToGUIPointWithDepth(point);
if (screenPos.z < 0)
return float.MaxValue;
return Mathf.Max(0, Vector2.Distance(screenPos, Event.current.mousePosition) - radius);
}
internal static Vector3 RoundBasedOnMinimumDifference(Vector3 position)
{
var minDiff = GetMinDifference(position);
position.x = RoundBasedOnMinimumDifference(position.x, minDiff.x);
position.y = RoundBasedOnMinimumDifference(position.y, minDiff.y);
position.z = RoundBasedOnMinimumDifference(position.z, minDiff.z);
return position;
}
internal static Vector3 GetMinDifference(Vector3 position)
{
return Vector3.one * (HandleUtility.GetHandleSize(position) / 80f);
}
internal static float RoundBasedOnMinimumDifference(float valueToRound, float minDifference)
{
var numberOfDecimals = Mathf.Clamp(-Mathf.FloorToInt(Mathf.Log10(Mathf.Abs(minDifference))), 0, k_MaxDecimals);
return (float)Math.Round(valueToRound, numberOfDecimals, MidpointRounding.AwayFromZero);
}
public static void GetNearestPointOnCurve(CurveData curve, out Vector3 position, out float t)
{
Vector3 closestA = Vector3.zero;
Vector3 closestB = Vector3.zero;
float closestDist = float.MaxValue;
int closestSegmentFirstPoint = -1;
GetCurveSegments(curve, s_ClosestPointArray);
for (int j = 0; j < s_ClosestPointArray.Length - 1; ++j)
{
Vector3 a = s_ClosestPointArray[j];
Vector3 b = s_ClosestPointArray[j + 1];
float dist = HandleUtility.DistanceToLine(a, b);
if (dist < closestDist)
{
closestA = a;
closestB = b;
closestDist = dist;
closestSegmentFirstPoint = j;
}
}
//Calculate position
Vector2 screenPosA = HandleUtility.WorldToGUIPoint(closestA);
Vector2 screenPosB = HandleUtility.WorldToGUIPoint(closestB);
Vector2 relativePoint = Event.current.mousePosition - screenPosA;
Vector2 lineDirection = screenPosB - screenPosA;
float length = lineDirection.magnitude;
float dot = Vector3.Dot(lineDirection, relativePoint);
if (length > .000001f)
dot /= length * length;
dot = Mathf.Clamp01(dot);
position = Vector3.Lerp(closestA, closestB, dot);
//Calculate percent on curve's segment
float percentPerSegment = 1.0f / (k_SegmentsPointCount - 1);
float percentA = closestSegmentFirstPoint * percentPerSegment;
float lengthAB = (closestB - closestA).magnitude;
float lengthAToClosest = (position - closestA).magnitude;
t = percentA + percentPerSegment * (lengthAToClosest / lengthAB);
}
internal static void GetCurveSegments(CurveData curve, Vector3[] results)
{
if (!curve.IsValid())
throw new ArgumentException(nameof(curve));
if (results == null)
throw new ArgumentNullException(nameof(results));
if (results.Length < 2)
throw new ArgumentException("Get curve segments requires a results array of at least two points", nameof(results));
var segmentCount = results.Length - 1;
float segmentPercentage = 1f / segmentCount;
var path = curve.a.spline;
for (int i = 0; i <= segmentCount; ++i)
{
results[i] = path.GetPointOnCurve(curve, i * segmentPercentage);
}
}
}
}

View file

@ -0,0 +1,15 @@
fileFormatVersion: 2
<<<<<<< HEAD:Editor/InternalBridge/AssemblyInfo.cs.meta
guid: 2da6d0ba602194831a1a64e5bc72f6ad
=======
guid: d678438588162a94e8d14b40288240af
>>>>>>> master:Editor/Utilities/SplineHandleUtility.cs.meta
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
using UnityEditor;
namespace UnityEditor.Splines
{
static class SplineSelectionUtility
{
internal static bool IsSelectable(IEditableSpline spline, int knotIndex, ISplineElement element)
{
if (element is EditableTangent)
{
var ownerKnot = spline.GetKnot(knotIndex);
if (ownerKnot is BezierEditableKnot knot)
{
// For open splines, tangentIn of first knot and tangentOut of last knot should not be selectable.
if (!spline.closed)
{
if ((knotIndex == 0 && knot.tangentIn == element) ||
(knotIndex == spline.knotCount - 1 && knot.tangentOut == element))
return false;
}
// Tangents should not be selectable if knot is Linear.
if (knot.mode == BezierEditableKnot.Mode.Linear)
return false;
}
}
return true;
}
}
}

View file

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