using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class CurveHandles
{
const float k_CurveLineWidth = 4f;
const float k_PreviewCurveOpacity = 0.5f;
static readonly Vector3[] s_CurveDrawingBuffer = new Vector3[SplineCacheUtility.CurveDrawResolution + 1];
static readonly Vector3[] s_FlowTriangleVertices = new Vector3[3];
///
/// Creates handles for a BezierCurve.
///
/// The controlID of the curve to create highlights for.
/// The to create handles for.
public static void Draw(int controlID, BezierCurve curve)
{
if(Event.current.type == EventType.Repaint)
Draw(controlID, curve, false, true);
}
///
/// Creates handles for a BezierCurve.
///
/// The to create handles for.
/// Whether the curve is part of the active spline.
internal static void Draw(BezierCurve curve, bool activeSpline)
{
if(Event.current.type == EventType.Repaint)
Draw(0, curve, false, activeSpline);
}
///
/// Creates highlights for a BezierCurve to make it easier to select.
///
/// The controlID of the curve to create highlights for.
/// The to create highlights for.
/// The (if any) that the curve belongs to.
/// The curve's index if it belongs to a spline - otherwise -1.
/// The knot at the start of the curve.
/// The knot at the end of the curve.
/// Whether the curve is part of the active spline.
internal static void DrawWithHighlight(
int controlID,
ISpline spline,
int curveIndex,
float4x4 localToWorld,
SelectableKnot knotA,
SelectableKnot knotB,
bool activeSpline)
{
var evt = Event.current;
switch(evt.GetTypeForControl(controlID))
{
case EventType.Layout:
case EventType.MouseMove:
if (!SplineHandles.ViewToolActive() && activeSpline)
{
var curve = spline.GetCurve(curveIndex).Transform(localToWorld);
var dist = DistanceToCurve(curve);
HandleUtility.AddControl(controlID, Mathf.Max(0, dist - SplineHandleUtility.pickingDistance));
//Trigger repaint on MouseMove to update highlight visuals from SplineHandles
if (evt.type == EventType.MouseMove || controlID == HandleUtility.nearestControl)
{
SplineHandleUtility.GetNearestPointOnCurve(curve, out _, out var t);
var curveMidT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, curveIndex);
var hoveredKnot = t <= curveMidT ? knotA : knotB;
if (!(SplineHandleUtility.lastHoveredElement is SelectableKnot knot) || !knot.Equals(hoveredKnot))
{
if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == controlID)
{
SplineHandleUtility.SetLastHoveredElement(hoveredKnot, controlID);
SceneView.RepaintAll();
}
}
}
}
break;
case EventType.MouseDown:
var curveMD = spline.GetCurve(curveIndex).Transform(localToWorld);
if (!SplineHandles.ViewToolActive() && HandleUtility.nearestControl == controlID)
{
//Clicking a knot selects it
if (evt.button != 0)
break;
GUIUtility.hotControl = controlID;
evt.Use();
SplineHandleUtility.GetNearestPointOnCurve(curveMD, out _, out var t);
SplineSelectionUtility.HandleSelection(t <= .5f ? knotA : knotB, false);
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
GUIUtility.hotControl = 0;
evt.Use();
}
break;
}
}
///
/// Draws flow on a BezierCurve to indicate the direction.
///
/// The to create highlights for.
/// The (if any) that the curve belongs to.
/// The curve's index if it belongs to a spline - otherwise -1.
internal static void DrawFlow(BezierCurve curve, ISpline spline, int curveIndex)
{
if(Event.current.type != EventType.Repaint)
return;
var arrow = SplineCacheUtility.GetCurveArrow(spline, curveIndex, curve);
s_FlowTriangleVertices[0] = arrow.positions[0];
s_FlowTriangleVertices[1] = arrow.positions[1];
s_FlowTriangleVertices[2] = arrow.positions[2];
using (new Handles.DrawingScope(SplineHandleUtility.lineColor, arrow.trs))
{
using (new ZTestScope(CompareFunction.Less))
Handles.DrawAAConvexPolygon(s_FlowTriangleVertices);
}
using (new Handles.DrawingScope(SplineHandleUtility.lineBehindColor, arrow.trs))
{
using (new ZTestScope(CompareFunction.Greater))
Handles.DrawAAConvexPolygon(s_FlowTriangleVertices);
}
}
static void Draw(int controlID, BezierCurve curve, bool preview, bool activeSpline)
{
var evt = Event.current;
switch (evt.type)
{
case EventType.Layout:
case EventType.MouseMove:
if (!SplineHandles.ViewToolActive() && activeSpline)
{
var dist = DistanceToCurve(curve);
HandleUtility.AddControl(controlID, Mathf.Max(0, dist - SplineHandleUtility.pickingDistance));
}
break;
case EventType.Repaint:
var prevColor = Handles.color;
FillCurveDrawingBuffer(curve);
var color = SplineHandleUtility.lineColor;
if (preview)
color.a *= k_PreviewCurveOpacity;
Handles.color = color;
using (new ZTestScope(CompareFunction.Less))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, s_CurveDrawingBuffer);
color = SplineHandleUtility.lineBehindColor;
if (preview)
color.a *= k_PreviewCurveOpacity;
Handles.color = color;
using (new ZTestScope(CompareFunction.Greater))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, s_CurveDrawingBuffer);
Handles.color = prevColor;
break;
}
}
static void FillCurveDrawingBuffer(BezierCurve curve)
{
SplineCacheUtility.GetCurvePositions(curve, s_CurveDrawingBuffer);
}
internal static float DistanceToCurve(BezierCurve curve)
{
FillCurveDrawingBuffer(curve);
return DistanceToCurve();
}
static float DistanceToCurve()
{
float dist = float.MaxValue;
for (var i = 0; i < s_CurveDrawingBuffer.Length - 1; ++i)
{
var a = s_CurveDrawingBuffer[i];
var b = s_CurveDrawingBuffer[i + 1];
dist = Mathf.Min(HandleUtility.DistanceToLine(a, b), dist);
}
return dist;
}
internal static void DoCurveHighlightCap(SelectableKnot knot)
{
if(Event.current.type != EventType.Repaint)
return;
if(knot.IsValid())
{
var spline = knot.SplineInfo.Spline;
var localToWorld = knot.SplineInfo.LocalToWorld;
if(knot.KnotIndex > 0 || spline.Closed)
{
var curve = spline.GetCurve(spline.PreviousIndex(knot.KnotIndex)).Transform(localToWorld);
var curveMiddleT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, spline.PreviousIndex(knot.KnotIndex));
DrawCurveHighlight(curve, 1f, curveMiddleT);
}
if(knot.KnotIndex < spline.Count - 1 || spline.Closed)
{
var curve = spline.GetCurve(knot.KnotIndex).Transform(localToWorld);
var curveMiddleT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, knot.KnotIndex);
DrawCurveHighlight(curve, 0f, curveMiddleT);
}
}
}
static void DrawCurveHighlight(BezierCurve curve, float startT, float endT)
{
FillCurveDrawingBuffer(curve);
var growing = startT <= endT;
var color = Handles.color;
color.a = growing ? 1f : 0f;
using (new ZTestScope(CompareFunction.Less))
using (new Handles.DrawingScope(color))
DrawAAPolyLineForCurveHighlight(color, startT, endT, 1f, growing);
using (new ZTestScope(CompareFunction.Greater))
using (new Handles.DrawingScope(color))
DrawAAPolyLineForCurveHighlight(color, startT, endT, 0.3f, growing);
}
static void DrawAAPolyLineForCurveHighlight(Color color, float startT, float endT, float colorAlpha, bool growing)
{
for (int i = 1; i <= SplineCacheUtility.CurveDrawResolution; ++i)
{
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, new[] { s_CurveDrawingBuffer[i - 1], s_CurveDrawingBuffer[i] });
var current = ((float)i / (float)SplineCacheUtility.CurveDrawResolution);
if (growing)
{
if (current > endT)
color.a = 0f;
else if (current > startT)
color.a = (1f - (current - startT) / (endT - startT)) * colorAlpha;
}
else
{
if (current < endT)
color.a = 0f;
else if (current > endT && current < startT)
color.a = (current - endT) / (startT - endT) * colorAlpha;
}
Handles.color = color;
}
}
///
/// Creates the set of control points that make up a curve.
///
/// The to create control points for.
public static void DrawControlNet(BezierCurve curve)
{
Handles.color = Color.green;
Handles.DotHandleCap(-1, curve.P0, Quaternion.identity, HandleUtility.GetHandleSize(curve.P0) * .04f, Event.current.type);
Handles.color = Color.red;
Handles.DotHandleCap(-1, curve.P1, Quaternion.identity, HandleUtility.GetHandleSize(curve.P1) * .04f, Event.current.type);
Handles.color = Color.yellow;
Handles.DotHandleCap(-1, curve.P2, Quaternion.identity, HandleUtility.GetHandleSize(curve.P2) * .04f, Event.current.type);
Handles.color = Color.blue;
Handles.DotHandleCap(-1, curve.P3, Quaternion.identity, HandleUtility.GetHandleSize(curve.P3) * .04f, Event.current.type);
Handles.color = Color.gray;
Handles.DrawDottedLine(curve.P0, curve.P1, 2f);
Handles.DrawDottedLine(curve.P1, curve.P2, 2f);
Handles.DrawDottedLine(curve.P2, curve.P3, 2f);
}
}
}