286 lines
11 KiB
C#
286 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using UnityEngine.Splines;
|
|
|
|
namespace UnityEditor.Splines
|
|
{
|
|
/// <summary>
|
|
/// Provides methods for drawing <see cref="SplineData"/> manipulation handles.
|
|
/// </summary>
|
|
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 bool s_AddingDataPoint = false;
|
|
|
|
static bool m_ShowAddHandle;
|
|
static float3 m_Position;
|
|
static float m_T;
|
|
|
|
/// <summary>
|
|
/// Draw default manipulation handles that enables adding, removing and moving
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="spline">The Spline to use to interpret the SplineData.</param>
|
|
/// <param name="splineData">The SplineData for which the handles are drawn.</param>
|
|
/// <typeparam name="TSpline">The Spline type.</typeparam>
|
|
/// <typeparam name="TData">The type of data this data point stores.</typeparam>
|
|
public static void DataPointHandles<TSpline, TData>(
|
|
this TSpline spline,
|
|
SplineData<TData> splineData)
|
|
where TSpline : ISpline
|
|
{
|
|
var evt = Event.current;
|
|
if(evt.type == EventType.MouseMove)
|
|
{
|
|
//Compute distance to spline and closest point
|
|
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
|
var distance = SplineUtility.GetNearestPoint(spline, ray, out m_Position, out m_T);
|
|
m_ShowAddHandle = distance < HandleUtility.GetHandleSize(m_Position);
|
|
}
|
|
|
|
//Id has to be consistent no matter the distance test
|
|
var id = GUIUtility.GetControlID(FocusType.Passive);
|
|
|
|
//Only activating the tooling when close enough from the spline
|
|
if(m_ShowAddHandle)
|
|
DataPointAddHandle(id, spline, splineData, m_Position, m_T);
|
|
|
|
//Remove DataPoint functionality
|
|
TryRemoveDataPoint(splineData);
|
|
|
|
//Draw Default manipulation handles
|
|
DataPointMoveHandles(spline, splineData);
|
|
}
|
|
|
|
static void TryRemoveDataPoint<TData>(SplineData<TData> splineData)
|
|
{
|
|
var evt = Event.current;
|
|
//Remove data point only when not adding one and when using right click button
|
|
if(!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);
|
|
evt.Use();
|
|
}
|
|
}
|
|
|
|
static void DataPointAddHandle<TSpline, TData>(
|
|
int controlID,
|
|
TSpline spline,
|
|
SplineData<TData> splineData,
|
|
float3 pos,
|
|
float t)
|
|
where TSpline : ISpline
|
|
{
|
|
Event evt = Event.current;
|
|
EventType eventType = evt.GetTypeForControl(controlID);
|
|
|
|
switch (eventType)
|
|
{
|
|
case EventType.Layout:
|
|
{
|
|
if(!Tools.viewToolActive)
|
|
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(pos, 0.1f));
|
|
break;
|
|
}
|
|
|
|
case EventType.Repaint:
|
|
if(HandleUtility.nearestControl == controlID && GUIUtility.hotControl == 0 || s_AddingDataPoint)
|
|
{
|
|
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
|
|
&& HandleUtility.nearestControl == controlID
|
|
&& GUIUtility.hotControl == 0)
|
|
{
|
|
s_AddingDataPoint = true;
|
|
var index = SplineUtility.ConvertIndexUnit(
|
|
spline, t,
|
|
splineData.PathIndexUnit);
|
|
|
|
s_NewDataPointIndex = splineData.AddDataPointWithDefaultValue(index);
|
|
evt.Use();
|
|
}
|
|
break;
|
|
|
|
case EventType.MouseDrag:
|
|
if (evt.button == 0 && s_AddingDataPoint)
|
|
{
|
|
GUIUtility.hotControl = 0;
|
|
var index = SplineUtility.ConvertIndexUnit(
|
|
spline, t,
|
|
splineData.PathIndexUnit);
|
|
s_NewDataPointIndex = splineData.MoveDataPoint(s_NewDataPointIndex, index);
|
|
evt.Use();
|
|
}
|
|
break;
|
|
|
|
case EventType.MouseUp:
|
|
if (evt.button == 0 && s_AddingDataPoint)
|
|
{
|
|
s_AddingDataPoint = false;
|
|
s_NewDataPointIndex = -1;
|
|
GUIUtility.hotControl = 0;
|
|
evt.Use();
|
|
}
|
|
break;
|
|
|
|
case EventType.MouseMove:
|
|
HandleUtility.Repaint();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void DataPointMoveHandles<TSpline, TData>(TSpline spline, SplineData<TData> 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 id = GUIUtility.GetControlID(FocusType.Passive);
|
|
|
|
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<TSpline, TData>(
|
|
int controlID,
|
|
TSpline spline,
|
|
SplineData<TData> 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
|
|
&& HandleUtility.nearestControl == controlID
|
|
&& GUIUtility.hotControl == 0)
|
|
{
|
|
GUIUtility.hotControl = controlID;
|
|
newTime = GetClosestSplineDataT(spline, splineData);
|
|
evt.Use();
|
|
}
|
|
break;
|
|
|
|
case EventType.MouseDrag:
|
|
if (GUIUtility.hotControl == controlID)
|
|
{
|
|
newTime = GetClosestSplineDataT(spline, splineData);
|
|
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,TData>(TSpline spline, SplineData<TData> 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);
|
|
}
|
|
}
|
|
}
|