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);
}
}
}