using System;
using System.Linq;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
///
/// Provides default handles to SplineData.
/// Call in your Editor Tool to add default handles
/// for you to add, move, and remove SplineData's DataPoints along a spline.
///
public static class SplineDataHandles
{
const float k_HandleSize = 0.15f;
const int k_PickRes = 2;
static int[] s_DataPointsIDs;
static int s_NewDataPointIndex = -1;
static float s_AddingDataPoint = float.NaN;
///
/// Creates manipulation handles in the SceneView to add, move, and remove SplineData's DataPoints along a spline.
/// DataPoints of the targeted SplineData along a Spline. Left click on an empty location
/// on the spline adds a new DataPoint in the SplineData. Left click on an existing DataPoint
/// allows to move this point along the Spline while a right click on it allows to delete that DataPoint.
///
///
/// Left-click an empty location on the spline to add a new DataPoint to the SplineData.
/// Left-click on a DataPoint to move the point along the Spline. Right-click a DataPoint to delete it.
///
/// The Spline to use to interprete the SplineData.
/// The SplineData for which the handles are drawn.
/// Either to use default value or closer DataPoint value when adding new DataPoint.
/// The Spline type.
/// The type of data this data point stores.
public static void DataPointHandles(
this TSpline spline,
SplineData splineData,
bool useDefaultValueOnAdd = false)
where TSpline : ISpline
{
spline.DataPointHandles(splineData, useDefaultValueOnAdd, 0);
}
///
/// Creates manipulation handles in the Scene view that can be used to add, move, and remove SplineData's DataPoints along a spline.
///
///
/// Left-click an empty location on the spline to add a new DataPoint to the SplineData.
/// Left-click and drag a DataPoint to move the point along the spline. Right-click a DataPoint to delete it.
///
/// The spline to use to interprete the SplineData.
/// The SplineData for which the handles are drawn.
/// The ID for the spline.
/// Whether to use the default value or a closer DataPoint value when adding new DataPoint.
/// The spline type.
/// The type of data this data point stores.
public static void DataPointHandles(
this TSpline spline,
SplineData splineData,
bool useDefaultValueOnAdd,
int splineID = 0)
where TSpline : ISpline
{
var id = GUIUtility.GetControlID(FocusType.Passive);
DataPointAddHandle(id, spline, splineData, useDefaultValueOnAdd, splineID);
// Draw Default manipulation handles
DataPointMoveHandles(spline, splineData);
// Remove DataPoint functionality
TryRemoveDataPoint(splineData);
}
static void TryRemoveDataPoint(SplineData splineData)
{
var evt = Event.current;
//Remove data point only when not adding one and when using right click button
if(float.IsNaN(s_AddingDataPoint) && GUIUtility.hotControl == 0
&& evt.type == EventType.MouseDown && evt.button == 1
&& s_DataPointsIDs.Contains(HandleUtility.nearestControl))
{
var dataPointIndex = splineData.Indexes.ElementAt(Array.IndexOf(s_DataPointsIDs, HandleUtility.nearestControl));
splineData.RemoveDataPoint(dataPointIndex);
GUI.changed = true;
evt.Use();
}
}
static bool IsHotControl(float splineID)
{
return !float.IsNaN(s_AddingDataPoint) && splineID.Equals(s_AddingDataPoint);
}
static void DataPointAddHandle(
int controlID,
TSpline spline,
SplineData splineData,
bool useDefaultValueOnAdd,
int splineID)
where TSpline : ISpline
{
Event evt = Event.current;
EventType eventType = evt.GetTypeForControl(controlID);
switch (eventType)
{
case EventType.Layout:
{
if (!SplineHandles.ViewToolActive())
{
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
SplineUtility.GetNearestPoint(spline, ray, out var pos, out _);
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(pos, 0.1f));
}
break;
}
case EventType.Repaint:
if ((HandleUtility.nearestControl == controlID && GUIUtility.hotControl == 0 && float.IsNaN(s_AddingDataPoint)) || IsHotControl(splineID))
{
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
SplineUtility.GetNearestPoint(spline, ray, out var pos, out var t);
var upDir = spline.EvaluateUpVector(t);
Handles.CircleHandleCap(controlID, pos, Quaternion.LookRotation(upDir), 0.15f * HandleUtility.GetHandleSize(pos), EventType.Repaint);
}
break;
case EventType.MouseDown:
if (evt.button == 0
&& !SplineHandles.ViewToolActive()
&& HandleUtility.nearestControl == controlID
&& GUIUtility.hotControl == 0)
{
s_AddingDataPoint = splineID;
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
SplineUtility.GetNearestPoint(spline, ray, out _, out var t);
var index = SplineUtility.ConvertIndexUnit(
spline, t,
splineData.PathIndexUnit);
s_NewDataPointIndex = splineData.AddDataPointWithDefaultValue(index, useDefaultValueOnAdd);
GUI.changed = true;
evt.Use();
}
break;
case EventType.MouseDrag:
if (evt.button == 0 && IsHotControl(splineID))
{
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
SplineUtility.GetNearestPoint(spline, ray, out _, out var t);
var index = SplineUtility.ConvertIndexUnit(
spline, t,
splineData.PathIndexUnit);
s_NewDataPointIndex = splineData.MoveDataPoint(s_NewDataPointIndex, index);
GUI.changed = true;
evt.Use();
}
break;
case EventType.MouseUp:
if (evt.button == 0 && IsHotControl(splineID))
{
s_AddingDataPoint = float.NaN;
s_NewDataPointIndex = -1;
GUIUtility.hotControl = 0;
evt.Use();
}
break;
case EventType.MouseMove:
HandleUtility.Repaint();
break;
}
}
static void DataPointMoveHandles(TSpline spline, SplineData splineData)
where TSpline : ISpline
{
if(s_DataPointsIDs == null || s_DataPointsIDs.Length != splineData.Count)
s_DataPointsIDs = new int[splineData.Count];
//Cache all data point IDs
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
s_DataPointsIDs[dataIndex] = GUIUtility.GetControlID(FocusType.Passive);
//Draw all data points handles on the spline
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
{
var index = splineData.Indexes.ElementAt(dataIndex);
SplineDataHandle(
s_DataPointsIDs[dataIndex],
spline,
splineData,
index,
k_HandleSize,
out float newIndex);
if(GUIUtility.hotControl == s_DataPointsIDs[dataIndex])
{
var newDataIndex = splineData.MoveDataPoint(dataIndex, newIndex);
// If the current DataPoint is moved across another DataPoint, then update the hotControl ID
if(newDataIndex - index != 0)
GUIUtility.hotControl = s_DataPointsIDs[newDataIndex];
}
}
}
static void SplineDataHandle(
int controlID,
TSpline spline,
SplineData splineData,
float dataPointIndex,
float size,
out float newTime) where TSpline : ISpline
{
newTime = dataPointIndex;
Event evt = Event.current;
EventType eventType = evt.GetTypeForControl(controlID);
var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPointIndex, splineData.PathIndexUnit);
var dataPosition = SplineUtility.EvaluatePosition(spline, normalizedT);
switch (eventType)
{
case EventType.Layout:
var dist = HandleUtility.DistanceToCircle(dataPosition, size * HandleUtility.GetHandleSize(dataPosition));
HandleUtility.AddControl(controlID, dist);
break;
case EventType.Repaint:
DrawSplineDataHandle(controlID, dataPosition, size);
break;
case EventType.MouseDown:
if (evt.button == 0
&& !SplineHandles.ViewToolActive()
&& HandleUtility.nearestControl == controlID
&& GUIUtility.hotControl == 0)
{
GUIUtility.hotControl = controlID;
newTime = GetClosestSplineDataT(spline, splineData);
GUI.changed = true;
evt.Use();
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlID)
{
newTime = GetClosestSplineDataT(spline, splineData);
GUI.changed = true;
evt.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
if(evt.button == 0)
{
GUIUtility.hotControl = 0;
newTime = GetClosestSplineDataT(spline, splineData);
}
evt.Use();
}
break;
case EventType.MouseMove:
HandleUtility.Repaint();
break;
}
}
static void DrawSplineDataHandle(int controlID, Vector3 position, float size)
{
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,
size * HandleUtility.GetHandleSize(position),
EventType.Repaint
);
}
}
// Spline must be in world space
static float GetClosestSplineDataT(TSpline spline, SplineData splineData) where TSpline : ISpline
{
var evt = Event.current;
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
SplineUtility.GetNearestPoint(spline,
ray,
out float3 _,
out float t,
k_PickRes);
return SplineUtility.ConvertIndexUnit(spline, t, splineData.PathIndexUnit);
}
}
}