using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; using UnityEngine.Rendering; using UnityEngine.Splines; namespace UnityEditor.Splines { /// /// This class provides the ability to draw a handle for a spline. /// public static class SplineHandles { /// /// The scope used to draw a spline. This is managing several purposes when using SplineHandles.DrawSomething(). /// This ensure selection is working properly, and that hovering an element is highlighting the correct related /// elements (for instance hovering a tangent highlights the opposite one when needed and the knot as well). /// public class SplineHandleScope : IDisposable { int m_NearestControl; /// /// Defines a new scope to draw spline elements in. /// public SplineHandleScope() { m_NearestControl = HandleUtility.nearestControl; Clear(); SplineHandleUtility.minElementId = GUIUtility.GetControlID(FocusType.Passive); } /// /// Called automatically when the `SplineHandleScope` is disposed. /// public void Dispose() { SplineHandleUtility.maxElementId = GUIUtility.GetControlID(FocusType.Passive); var evtType = Event.current.type; if ( (evtType == EventType.MouseMove || evtType == EventType.Layout) && HandleUtility.nearestControl == m_NearestControl) SplineHandleUtility.ResetLastHoveredElement(); } } /// /// The color of sections of spline curve handles that are behind objects in the Scene view. /// public static Color lineBehindColor => SplineHandleUtility.lineBehindColor; /// /// The color of sections of spline curves handles that are in front of objects in the Scene view. /// public static Color lineColor => SplineHandleUtility.lineColor; /// /// The color of tangent handles for a spline. /// public static Color tangentColor => SplineHandleUtility.tangentColor; /// /// The distance to pick a spline knot, tangent, or curve handle at. /// public static float pickingDistance => SplineHandleUtility.pickingDistance; static List s_ControlIDs = new(); static List s_CurveIDs = new(); static readonly List k_KnotBuffer = new (); static Dictionary s_KnotsIDs = new (); // todo Tools.viewToolActive should be handling the modifier check, but 2022.2 broke this internal static bool ViewToolActive() { return Tools.viewToolActive || Tools.current == Tool.View || (Event.current.modifiers & EventModifiers.Alt) == EventModifiers.Alt; } static void Clear() { s_CurveIDs.Clear(); s_KnotsIDs.Clear(); } /// /// Creates handles for a set of splines. These handles display the knots, tangents, and segments of a spline. /// These handles support selection and the direct manipulation of spline elements. /// /// The set of splines to draw handles for. public static void DoHandles(IReadOnlyList splines) { Profiler.BeginSample("SplineHandles.DoHandles"); using (new SplineHandleScope()) { // Drawing done in two separate passes to make sure the curves are drawn behind the spline elements. // Draw the curves. for (int i = 0; i < splines.Count; ++i) { DoSegmentsHandles(splines[i]); } DoKnotsAndTangentsHandles(splines); } Profiler.EndSample(); } internal static bool IsCurveId(int id) { return s_CurveIDs.Contains(id); } /// /// Creates knot and tangent handles for a spline. Call `DoKnotsAndTangentsHandles` in a `SplineHandleScope`. /// This method is used internally by `DoHandles`. /// /// The spline to create knot and tangent handles for. public static void DoKnotsAndTangentsHandles(SplineInfo spline) { SplineHandleUtility.UpdateElementColors(); KnotHandles.ClearVisibleKnots(); // Draw the spline elements. DrawSplineElements(spline); //Drawing knots on top of all other elements and above other splines KnotHandles.DrawVisibleKnots(); } /// /// Creates knot and tangent handles for multiple splines. Call `DoKnotsAndTangentsHandles` in a `SplineHandleScope`. /// This method is used internally by `DoHandles`. /// /// The splines to create knot and tangent handles for. public static void DoKnotsAndTangentsHandles(IReadOnlyList splines) { SplineHandleUtility.UpdateElementColors(); KnotHandles.ClearVisibleKnots(); // Draw the spline elements. for (int i = 0; i < splines.Count; ++i) DrawSplineElements(splines[i]); //Drawing knots on top of all other elements and above other splines KnotHandles.DrawVisibleKnots(); } /// /// Creates segment handles for a spline. Call `DoCurvesHandles` in a `SplineHandleScope`. /// This method is used internally by `DrawHandles`. /// /// The splineInfo of the spline to draw knots and tangents for. public static void DoSegmentsHandles(SplineInfo splineInfo) { var spline = splineInfo.Spline; if (spline == null || spline.Count < 2) return; var localToWorld = splineInfo.LocalToWorld; // If the spline isn't closed, skip the last index of the spline int lastIndex = spline.Closed ? spline.Count - 1 : spline.Count - 2; if (SplineHandleSettings.ShowMesh) { using (var nativeSpline = new NativeSpline(spline, localToWorld)) using (var mesh = new SplineMeshHandle()) using (new ZTestScope(UnityEngine.Rendering.CompareFunction.Less)) { mesh.Do(nativeSpline, SplineHandleSettings.SplineMeshSize, SplineHandleSettings.SplineMeshColor, SplineHandleSettings.SplineMeshResolution); } } s_ControlIDs.Clear(); for (int idIndex = 0; idIndex < lastIndex + 1; ++idIndex) { var id = GUIUtility.GetControlID(FocusType.Passive); s_ControlIDs.Add(id); s_CurveIDs.Add(id); } var drawHandlesAsActive = !SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(splineInfo); //Draw all the curves at once SplineCacheUtility.GetCachedPositions(spline, out var positions); using (new Handles.DrawingScope(SplineHandleUtility.lineColor, localToWorld)) { using (new ZTestScope(CompareFunction.Less)) Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, 4f, positions); } using (new Handles.DrawingScope(SplineHandleUtility.lineBehindColor, localToWorld)) { using (new ZTestScope(CompareFunction.Greater)) Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, 4f, positions); } if (drawHandlesAsActive) { for (int curveIndex = 0; curveIndex < lastIndex + 1; ++curveIndex) { if (SplineHandleSettings.FlowDirectionEnabled && Event.current.type == EventType.Repaint) { var curve = spline.GetCurve(curveIndex).Transform(localToWorld); CurveHandles.DrawFlow(curve, spline, curveIndex); } } } for (int curveIndex = 0; curveIndex < lastIndex + 1; ++curveIndex) { CurveHandles.DrawWithHighlight( s_ControlIDs[curveIndex], spline, curveIndex, localToWorld, new SelectableKnot(splineInfo, curveIndex), new SelectableKnot(splineInfo, SplineUtility.NextIndex(curveIndex, spline.Count, spline.Closed)), drawHandlesAsActive); } SplineHandleUtility.canDrawOnCurves = true; } static void DrawSplineElements(SplineInfo splineInfo) { var spline = splineInfo.Spline; var drawHandlesAsActive = !SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(splineInfo); if (drawHandlesAsActive) { for (int knotIndex = 0; knotIndex < spline.Count; ++knotIndex) DrawKnotWithTangentsHandles_Internal(new SelectableKnot(splineInfo, knotIndex)); } else { for (int knotIndex = 0; knotIndex < spline.Count; ++knotIndex) KnotHandles.DrawInformativeKnot(new SelectableKnot(splineInfo, knotIndex)); } } /// /// Creates handles for a knot and its tangents if those tangents are modifiable. /// These handles support the selection and direct manipulation of spline elements. /// Call `DoKnotWithTangentsHandles` in a `SplineHandleScope`. /// /// The knot to draw handles for. public static void DoKnotWithTangentsHandles(SelectableKnot knot) { KnotHandles.ClearVisibleKnots(); DrawKnotWithTangentsHandles_Internal(knot); KnotHandles.DrawVisibleKnots(); } static void DrawKnotWithTangentsHandles_Internal(SelectableKnot knot) { var splineInfo = knot.SplineInfo; if (SplineUtility.AreTangentsModifiable(splineInfo.Spline.GetTangentMode(knot.KnotIndex))) DoTangentsHandles(knot); DrawKnotHandles_Internal(knot); } /// /// Create handles for a knot. These handles the support selection and direct manipulation of spline elements. /// Call `DoKnotHandles` in a `SplineHandleScope`. /// /// The knot to draw handles for. public static void DoKnotHandles(SelectableKnot knot) { KnotHandles.ClearVisibleKnots(); DrawKnotHandles_Internal(knot); KnotHandles.DrawVisibleKnots(); } static void DrawKnotHandles_Internal(SelectableKnot knot) { var id = GetKnotID(knot); SelectionHandle(id, knot); KnotHandles.Draw(id, knot); } /// /// Create handles for a knot's tangents if those tangents are modifiable. `DoTangentsHandles` does not create handles for the knot. /// These handles support the selection and direct manipulation of the spline elements. /// Call `DoTangentsHandles` in a `SplineHandleScope`. /// /// The knot to draw tangent handles for. public static void DoTangentsHandles(SelectableKnot knot) { if(!knot.IsValid()) return; var splineInfo = knot.SplineInfo; var spline = splineInfo.Spline; var knotIndex = knot.KnotIndex; var tangentIn = new SelectableTangent(splineInfo, knotIndex, BezierTangent.In); var tangentOut = new SelectableTangent(splineInfo, knotIndex, BezierTangent.Out); var controlIdIn = GUIUtility.GetControlID(FocusType.Passive); var controlIdOut = GUIUtility.GetControlID(FocusType.Passive); // Tangent In if (GUIUtility.hotControl == controlIdIn || SplineHandleUtility.ShouldShowTangent(tangentIn) && (spline.Closed || knotIndex != 0)) { SelectionHandle(controlIdIn, tangentIn); TangentHandles.Draw(controlIdIn, tangentIn); } // Tangent Out if (GUIUtility.hotControl == controlIdOut || SplineHandleUtility.ShouldShowTangent(tangentOut) && (spline.Closed || knotIndex + 1 != spline.Count)) { SelectionHandle(controlIdOut, tangentOut); TangentHandles.Draw(controlIdOut, tangentOut); } } static int GetKnotID(SelectableKnot knot) { EditorSplineUtility.GetKnotLinks(knot, k_KnotBuffer); //If a linked knot as already been assigned, return the same id if (s_KnotsIDs.ContainsKey(k_KnotBuffer[0])) return s_KnotsIDs[k_KnotBuffer[0]]; //else compute a new id and record it var id = GUIUtility.GetControlID(FocusType.Passive); s_KnotsIDs.Add(k_KnotBuffer[0], id); return id; } static void SelectionHandle(int id, T element) where T : struct, ISelectableElement { Event evt = Event.current; EventType eventType = evt.GetTypeForControl(id); switch (eventType) { case EventType.Layout: case EventType.MouseMove: if (!ViewToolActive()) { HandleUtility.AddControl(id, SplineHandleUtility.DistanceToCircle(element.Position, SplineHandleUtility.pickingDistance)); if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == id) SplineHandleUtility.SetLastHoveredElement(element, id); } break; case EventType.MouseDown: if (HandleUtility.nearestControl == id && evt.button == 0) { GUIUtility.hotControl = id; evt.Use(); DirectManipulation.BeginDrag(element.Position, EditorSplineUtility.GetElementRotation(element)); } break; case EventType.MouseDrag: if (GUIUtility.hotControl == id) { EditorSplineUtility.RecordObject(element.SplineInfo, "Move Knot"); var pos = TransformOperation.ApplySmartRounding(DirectManipulation.UpdateDrag(id)); if (element is SelectableTangent tangent) EditorSplineUtility.ApplyPositionToTangent(tangent, pos); else element.Position = pos; GUI.changed = true; evt.Use(); } break; case EventType.MouseUp: if (GUIUtility.hotControl == id && evt.button == 0) { if (!DirectManipulation.IsDragging) SplineSelectionUtility.HandleSelection(element); DirectManipulation.EndDrag(); GUI.changed = true; evt.Use(); GUIUtility.hotControl = 0; } break; case EventType.Repaint: DirectManipulation.DrawHandles(id, element.Position); break; } } /// /// Draws a handle for a spline. /// /// The target spline. /// A type implementing ISpline. public static void DoSpline(T spline) where T : ISpline => DoSpline(-1, spline); /// /// Draws a handle for a spline. /// /// The spline mesh controlID. /// The target spline. /// A type implementing ISpline. public static void DoSpline(int controlID, T spline) where T : ISpline { for(int i = 0; i < spline.GetCurveCount(); ++i) CurveHandles.Draw(controlID, spline.GetCurve(i)); } /// /// Draws a handle for a . /// /// The to create handles for. public static void DoCurve(BezierCurve curve) => CurveHandles.Draw(-1, curve); /// /// Draws a handle for a . /// /// The spline mesh controlID. /// The to create handles for. public static void DoCurve(int controlID, BezierCurve curve) => CurveHandles.Draw(controlID, curve); /// /// Draws handles for a knot. These handles are drawn only during repaint events and not on selection. /// /// The to create handles for. /// Set to true to draw the knot handle as a selected element. /// Set to true to draw the knot handle as a hovered element. public static void DrawKnot(SelectableKnot knot, bool selected = false, bool hovered = false) => DrawKnot(-1, knot, selected, hovered); /// /// Draws handles for a knot. These handles are drawn only during repaint events and not on selection. /// /// The controlID of the tangent to create handles for. /// The to create handles for. /// Set to true to draw the knot handle as a selected element. /// Set to true to draw the knot handle as a hovered element. public static void DrawKnot(int controlID, SelectableKnot knot, bool selected = false, bool hovered = false) { KnotHandles.Do(controlID, knot, selected, hovered); } /// /// Draws handles for a tangent. These handles are drawn only during repaint events and not on selection. /// /// The to create handles for. /// Set to true to draw the tangent handle as a selected element. /// Set to true to draw the tangent handle as a hovered element. public static void DrawTangent(SelectableTangent tangent, bool selected = false, bool hovered = false) => DrawTangent(-1, tangent, selected, hovered); /// /// Draws handles for a tangent. These handles are drawn only during repaint events and not on selection. /// /// The controlID of the tangent to create handles for. /// The to create handles for. /// Set to true to draw the tangent handle as a selected element. /// Set to true to draw the tangent handle as a hovered element. public static void DrawTangent(int controlID, SelectableTangent tangent, bool selected = false, bool hovered = false) { TangentHandles.Do(controlID, tangent, selected, hovered); } } }