using System; using System.Reflection; using Unity.Mathematics; using UnityEngine; using UnityEngine.Splines; namespace UnityEditor.Splines { static class SplineDataHandlesDrawer { /// /// LabelType used to define if label must be displayed along the handle and how it should be formatted /// internal enum LabelType { None, Index, Time }; const float k_HandleSize = 0.15f; const int k_PickRes = 2; internal static void InitCustomHandles( SplineData splineData, ISplineDataHandle drawerInstance) { var ids = new int[splineData.Count]; for(int kfIndex = 0; kfIndex < splineData.Count; kfIndex++) ids[kfIndex] = GUIUtility.GetControlID(FocusType.Passive); ( (SplineDataHandle)drawerInstance ).m_ControlIDs = ids; } internal static void DrawCustomHandles( SplineData splineData, Component component, Spline spline, Matrix4x4 localToWorld, Color color, ISplineDataHandle drawerInstance, MethodInfo splineDataDrawMethodInfo, MethodInfo keyframeDrawMethodInfo) { if(splineData.Count == 0) return; Undo.RecordObject(component, "Modifying Spline Data Points"); //Invoke if existing the custom drawer for the whole spline splineDataDrawMethodInfo?.Invoke(drawerInstance, new object[] { splineData, spline, localToWorld, color }); if (keyframeDrawMethodInfo == null) return; var native = new NativeSpline(spline, localToWorld); var ids = ( (SplineDataHandle)drawerInstance ).controlIDs; for(int splineDataIndex = 0; splineDataIndex < splineData.Count; splineDataIndex++) { var keyframe = splineData[splineDataIndex]; var normalizedT = SplineUtility.GetNormalizedInterpolation(native, keyframe.Index, splineData.PathIndexUnit); native.Evaluate(normalizedT, out float3 dataPosition, out float3 dataDirection, out float3 dataUp); using(new Handles.DrawingScope(color)) { //Invoke if existing the custom drawer for each keyframe keyframeDrawMethodInfo?.Invoke(drawerInstance, new object[] { ids[splineDataIndex], (Vector3)dataPosition, (Vector3)dataDirection, (Vector3)dataUp, splineData, splineDataIndex }); } } native.Dispose(); } internal static void DrawSplineDataHandles( SplineData splineData, Component component, Spline spline, Matrix4x4 localToWorld, Color color, LabelType labelType) { Undo.RecordObject(component, "Modifying Spline Data Points"); using var native = new NativeSpline(spline, localToWorld); using(new Handles.DrawingScope(color)) { for(int keyframeIndex = 0; keyframeIndex < splineData.Count; keyframeIndex++) { var keyframe = splineData[keyframeIndex]; var inUse = SplineDataHandle(splineData, keyframe, native, labelType, keyframeIndex, out float time); if(inUse) { keyframe.Index = time; splineData.SetDataPointNoSort(keyframeIndex, keyframe); //OnMouseUp event if(GUIUtility.hotControl == 0) splineData.SortIfNecessary(); } } } } internal static bool SplineDataHandle( SplineData splineData, IDataPoint dataPoint, NativeSpline spline, LabelType labelType, int keyframeIndex, out float newTime) { int id = GUIUtility.GetControlID(FocusType.Passive); Event evt = Event.current; EventType eventType = evt.GetTypeForControl(id); var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPoint.Index, splineData.PathIndexUnit); var dataPosition = SplineUtility.EvaluatePosition(spline, normalizedT); switch (eventType) { case EventType.Layout: { if(!Tools.viewToolActive) { var dist = HandleUtility.DistanceToCircle(dataPosition, k_HandleSize * HandleUtility.GetHandleSize(dataPosition)); HandleUtility.AddControl(id, dist); } break; } case EventType.Repaint: DrawSplineDataHandle(dataPosition, id); DrawSplineDataLabel(dataPosition, labelType, dataPoint, keyframeIndex); break; case EventType.MouseDown: if (evt.button == 0 && HandleUtility.nearestControl == id && GUIUtility.hotControl == 0) { GUIUtility.hotControl = id; evt.Use(); newTime = GetClosestSplineDataT(spline, splineData); return true; } break; case EventType.MouseDrag: if (GUIUtility.hotControl == id) { evt.Use(); newTime = GetClosestSplineDataT(spline, splineData); return true; } break; case EventType.MouseUp: if (GUIUtility.hotControl == id) { evt.Use(); if(evt.button == 0) { GUIUtility.hotControl = 0; newTime = GetClosestSplineDataT(spline, splineData); return true; } } break; case EventType.MouseMove: HandleUtility.Repaint(); break; } newTime = dataPoint.Index; return false; } static void DrawSplineDataHandle(Vector3 position, int controlID) { var handleColor = Handles.color; if(controlID == GUIUtility.hotControl) handleColor = Handles.selectedColor; else if(GUIUtility.hotControl == 0 && controlID == HandleUtility.nearestControl) handleColor = Handles.preselectionColor; // to avoid affecting the sphere dimensions with the handles matrix, we'll just use the position and reset // the matrix to identity when drawing. position = Handles.matrix * position; using(new Handles.DrawingScope(handleColor, Matrix4x4.identity)) { Handles.SphereHandleCap( controlID, position, Quaternion.identity, k_HandleSize * HandleUtility.GetHandleSize(position), EventType.Repaint ); } } static void DrawSplineDataLabel(Vector3 position, LabelType labelType, IDataPoint dataPoint, int keyframeIndex) { if(labelType == LabelType.None) return; float labelVal = dataPoint.Index; if(labelType == LabelType.Index && keyframeIndex >= 0) labelVal = keyframeIndex; var label = ( Mathf.RoundToInt(labelVal * 100) / 100f ).ToString(); label = labelType == LabelType.Index ? "[" + label + "]" : "t: "+label; Handles.Label(position - 0.1f * Vector3.up, label); } // Spline must be in world space static float GetClosestSplineDataT(NativeSpline spline, SplineData splineData) { var evt = Event.current; var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition); SplineUtility.GetNearestPoint(spline, ray, out float3 _, out float t, k_PickRes); var time = SplineUtility.ConvertIndexUnit( spline, t, PathIndexUnit.Normalized, splineData.PathIndexUnit); return time; } } }