Rasagar/Library/PackageCache/com.unity.2d.sprite/Editor/ShapeEditor/ShapeEditor.cs
2024-08-26 23:07:20 +03:00

1113 lines
44 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditorInternal;
using UnityEngine.U2D.Sprites;
namespace UnityEditor.U2D.Sprites
{
internal interface IShapeEditorFactory
{
ShapeEditor CreateShapeEditor();
}
internal class ShapeEditorFactory : IShapeEditorFactory
{
public ShapeEditor CreateShapeEditor()
{
return new ShapeEditor(new GUIUtilitySystem(), new EventSystem());
}
}
internal class ShapeEditor
{
public delegate float DistanceToControl(Vector3 pos, Quaternion rotation, float handleSize);
internal enum SelectionType { Normal, Additive, Subtractive }
internal enum Tool { Edit, Create, Break }
internal enum TangentMode { Linear, Continuous, Broken }
// --- To use this class in your editor, you need to implement these: (see ShapeEditorTests for example)
// --- Data
public Func<int, Vector3> GetPointPosition = i => Vector3.zero;
public Action<int, Vector3> SetPointPosition = (i, p) => {};
public Func<int, Vector3> GetPointLTangent = i => Vector3.zero;
public Action<int, Vector3> SetPointLTangent = (i, p) => {};
public Func<int, Vector3> GetPointRTangent = i => Vector3.zero;
public Action<int, Vector3> SetPointRTangent = (i, p) => {};
public Func<int, TangentMode> GetTangentMode = i => TangentMode.Linear;
public Action<int, TangentMode> SetTangentMode = (i, m) => {};
public Action<int, Vector3> InsertPointAt = (i, p) => {};
public Action<int> RemovePointAt = i => {};
public Func<int> GetPointsCount = () => 0;
// --- Transforms
public Func<Vector2, Vector3> ScreenToLocal = i => i;
public Func<Vector3, Vector2> LocalToScreen = i => i;
public Func<Matrix4x4> LocalToWorldMatrix = () => Matrix4x4.identity;
// --- Distance functions
public Func<DistanceToControl> DistanceToRectangle = () => HandleUtility.DistanceToRectangle;
public Func<DistanceToControl> DistanceToDiamond = () => HandleUtility.DistanceToDiamond;
public Func<DistanceToControl> DistanceToCircle = () => DistanceToCircleInternal;
// --- Other
public Action Repaint = () => {};
public Action RecordUndo = () => {};
public Func<Vector3, Vector3> Snap = i => i;
public Action<Bounds> Frame = b => {};
public Action<int> OnPointClick = i => {};
public Func<bool> OpenEnded = () => false;
public Func<float> GetHandleSize = () => 5f;
// --- END
public ITexture2D lineTexture { get; set; }
public int activePoint { get; set; }
public HashSet<int> selectedPoints { get { return m_Selection.indices; } }
public bool inEditMode { get; set; }
public int activeEdge { get { return m_ActiveEdge; } set { m_ActiveEdge = value; } }
// Shape editor needs to reset its state on next OnSceneGUI. Reset can't be called immediately elsewhere, because we need to also reset local sceneview state like GUIUtility.keyboardControl
public bool delayedReset { set { m_DelayedReset = value; } }
private ShapeEditorSelection m_Selection;
private Vector2 m_MousePositionLastMouseDown;
private int m_ActivePointOnLastMouseDown = -1;
private int m_NewPointIndex = -1;
private Vector3 m_EdgeDragStartMousePosition;
private Vector3 m_EdgeDragStartP0;
private Vector3 m_EdgeDragStartP1;
private bool m_NewPointDragFinished;
private int m_ActiveEdge = -1;
private bool m_DelayedReset = false;
private HashSet<ShapeEditor> m_ShapeEditorListeners = new HashSet<ShapeEditor>();
private ShapeEditorRectSelectionTool m_RectSelectionTool;
private int m_MouseClosestEdge = -1;
private float m_MouseClosestEdgeDist = float.MaxValue;
private int m_ShapeEditorRegisteredTo = 0;
private int m_ShapeEditorUpdateDone = 0;
private static Color handleOutlineColor { get; set; }
private static Color handleFillColor { get; set; }
private Quaternion handleMatrixrotation { get { return Quaternion.LookRotation(handles.matrix.GetColumn(2), handles.matrix.GetColumn(1)); } }
private IGUIUtility guiUtility { get; set; }
private IEventSystem eventSystem { get; set; }
private IEvent currentEvent { get; set; }
private IGL glSystem { get; set; }
private IHandles handles { get; set; }
private Dictionary<DrawBatchDataKey, List<Vector3>> m_DrawBatch;
private Vector3[][] m_EdgePoints;
enum ColorEnum
{
EUnselected,
EUnselectedHovered,
ESelected,
ESelectedHovered
}
private static readonly Color[] k_OutlineColor = new[]
{
Color.gray,
Color.white,
new Color(34f / 255f, 171f / 255f, 1f), // #22abff
Color.white
};
static readonly Color[] k_FillColor = new[]
{
Color.white,
new Color(131f / 255f, 220f / 255f, 1f), // #83dcff
new Color(34f / 255f, 171f / 255f, 1f), // #22abff
new Color(34f / 255f, 171f / 255f, 1f) // #22abff
};
private static readonly Color k_TangentColor = new Color(34f / 255f, 171f / 255f, 1f); // #22abff
private static readonly Color k_TangentColorAlternative = new Color(131f / 255f, 220f / 255f, 1f); // #83dcff
private const float k_EdgeHoverDistance = 9f;
private const float k_EdgeWidth = 2f;
private const float k_ActiveEdgeWidth = 6f;
private const float k_MinExistingPointDistanceForInsert = 20f;
private readonly int k_CreatorID;
private readonly int k_EdgeID;
private readonly int k_RightTangentID;
private readonly int k_LeftTangentID;
private const int k_BezierPatch = 40;
class DrawBatchDataKey
{
public Color color { get; private set; }
public int glMode { get; private set; }
private int m_Hash;
public DrawBatchDataKey(Color c , int mode)
{
color = c;
glMode = mode;
m_Hash = glMode ^ (color.GetHashCode() << 2);
}
public override int GetHashCode()
{
return m_Hash;
}
public override bool Equals(object obj)
{
var key = obj as DrawBatchDataKey;
if (ReferenceEquals(key, null))
return false;
return m_Hash == obj.GetHashCode();
}
}
public ShapeEditor(IGUIUtility gu, IEventSystem es)
{
m_Selection = new ShapeEditorSelection(this);
guiUtility = gu;
eventSystem = es;
k_CreatorID = guiUtility.GetPermanentControlID();
k_EdgeID = guiUtility.GetPermanentControlID();
k_RightTangentID = guiUtility.GetPermanentControlID();
k_LeftTangentID = guiUtility.GetPermanentControlID();
glSystem = GLSystem.GetSystem();
handles = HandlesSystem.GetSystem();
}
public void SetRectSelectionTool(ShapeEditorRectSelectionTool sers)
{
if (m_RectSelectionTool != null)
{
m_RectSelectionTool.RectSelect -= SelectPointsInRect;
m_RectSelectionTool.ClearSelection -= ClearSelectedPoints;
}
m_RectSelectionTool = sers;
m_RectSelectionTool.RectSelect += SelectPointsInRect;
m_RectSelectionTool.ClearSelection += ClearSelectedPoints;
}
public void OnDisable()
{
m_RectSelectionTool.RectSelect -= SelectPointsInRect;
m_RectSelectionTool.ClearSelection -= ClearSelectedPoints;
m_RectSelectionTool = null;
}
private void PrepareDrawBatch()
{
if (currentEvent.type == EventType.Repaint)
{
m_DrawBatch = new Dictionary<DrawBatchDataKey, List<Vector3>>();
}
}
private void DrawBatch()
{
if (currentEvent.type == EventType.Repaint)
{
HandleUtility.ApplyWireMaterial();
glSystem.PushMatrix();
glSystem.MultMatrix(handles.matrix);
foreach (var drawBatch in m_DrawBatch)
{
glSystem.Begin(drawBatch.Key.glMode);
glSystem.Color(drawBatch.Key.color);
foreach (var t in drawBatch.Value)
{
glSystem.Vertex(t);
}
glSystem.End();
}
glSystem.PopMatrix();
}
}
List<Vector3> GetDrawBatchList(DrawBatchDataKey key)
{
List<Vector3> data = null;
if (!m_DrawBatch.ContainsKey(key))
{
m_DrawBatch.Add(key, new List<Vector3>());
}
data = m_DrawBatch[key];
return data;
}
public void OnGUI()
{
DelayedResetIfNecessary();
currentEvent = eventSystem.current;
if (currentEvent.type == EventType.MouseDown)
StoreMouseDownState();
var oldColor = handles.color;
var oldMatrix = handles.matrix;
handles.matrix = LocalToWorldMatrix();
PrepareDrawBatch();
Edges();
if (inEditMode)
{
Framing();
Tangents();
Points();
}
DrawBatch();
handles.color = oldColor;
handles.matrix = oldMatrix;
OnShapeEditorUpdateDone();
foreach (var se in m_ShapeEditorListeners)
se.OnShapeEditorUpdateDone();
}
private void Framing()
{
if (currentEvent.commandName == EventCommandNames.FrameSelected && m_Selection.Count > 0)
{
switch (currentEvent.type)
{
case EventType.ExecuteCommand:
Bounds bounds = new Bounds(GetPointPosition(selectedPoints.First()), Vector3.zero);
foreach (var index in selectedPoints)
bounds.Encapsulate(GetPointPosition(index));
Frame(bounds);
goto case EventType.ValidateCommand;
case EventType.ValidateCommand:
currentEvent.Use();
break;
}
}
}
void PrepareEdgePointList()
{
if (m_EdgePoints == null)
{
var total = this.GetPointsCount();
int loopCount = OpenEnded() ? total - 1 : total;
m_EdgePoints = new Vector3[loopCount][];
int index = mod(total - 1, loopCount);
for (int loop = mod(index + 1, total); loop < total; index = loop, ++loop)
{
var position0 = this.GetPointPosition(index);
var position1 = this.GetPointPosition(loop);
if (GetTangentMode(index) == TangentMode.Linear && GetTangentMode(loop) == TangentMode.Linear)
{
m_EdgePoints[index] = new[] { position0, position1 };
}
else
{
var tangent0 = GetPointRTangent(index) + position0;
var tangent1 = GetPointLTangent(loop) + position1;
m_EdgePoints[index] = handles.MakeBezierPoints(position0, position1, tangent0, tangent1, k_BezierPatch);
}
}
}
}
float DistancePointEdge(Vector3 point, Vector3[] edge)
{
float result = float.MaxValue;
int index = edge.Length - 1;
for (int nextIndex = 0; nextIndex < edge.Length; index = nextIndex, nextIndex++)
{
float dist = HandleUtility.DistancePointLine(point, edge[index], edge[nextIndex]);
if (dist < result)
result = dist;
}
return result;
}
private float GetMouseClosestEdgeDistance()
{
var mousePosition = ScreenToLocal(eventSystem.current.mousePosition);
var total = this.GetPointsCount();
if (m_MouseClosestEdge == -1 && total > 0)
{
PrepareEdgePointList();
m_MouseClosestEdgeDist = float.MaxValue;
int loopCount = OpenEnded() ? total - 1 : total;
for (int loop = 0; loop < loopCount; loop++)
{
var dist = DistancePointEdge(mousePosition, m_EdgePoints[loop]);
if (dist < m_MouseClosestEdgeDist)
{
m_MouseClosestEdge = loop;
m_MouseClosestEdgeDist = dist;
}
}
}
if (guiUtility.hotControl == k_CreatorID || guiUtility.hotControl == k_EdgeID)
return float.MinValue;
return m_MouseClosestEdgeDist;
}
public void Edges()
{
var otherClosestEdgeDistance = float.MaxValue;
if (m_ShapeEditorListeners.Count > 0)
otherClosestEdgeDistance = (from se in m_ShapeEditorListeners select se.GetMouseClosestEdgeDistance()).Max();
float edgeDistance = GetMouseClosestEdgeDistance();
bool closestEdgeHighlight = EdgeDragModifiersActive() && edgeDistance < k_EdgeHoverDistance && edgeDistance < otherClosestEdgeDistance;
if (currentEvent.type == EventType.Repaint)
{
Color handlesOldColor = handles.color;
PrepareEdgePointList();
var total = this.GetPointsCount();
int loopCount = OpenEnded() ? total - 1 : total;
for (int loop = 0; loop < loopCount; loop++)
{
Color edgeColor = loop == m_ActiveEdge ? Color.yellow : Color.white;
float edgeWidth = loop == m_ActiveEdge || (m_MouseClosestEdge == loop && closestEdgeHighlight) ? k_ActiveEdgeWidth : k_EdgeWidth;
handles.color = edgeColor;
handles.DrawAAPolyLine(lineTexture, edgeWidth, m_EdgePoints[loop]);
}
handles.color = handlesOldColor;
}
if (inEditMode)
{
if (otherClosestEdgeDistance > edgeDistance)
{
var farEnoughFromExistingPoints = MouseDistanceToPoint(FindClosestPointToMouse()) > k_MinExistingPointDistanceForInsert;
var farEnoughtFromActiveTangents = MouseDistanceToClosestTangent() > k_MinExistingPointDistanceForInsert;
var farEnoughFromExisting = farEnoughFromExistingPoints && farEnoughtFromActiveTangents;
var hoveringEdge = m_MouseClosestEdgeDist < k_EdgeHoverDistance;
var handleEvent = hoveringEdge && farEnoughFromExisting && !m_RectSelectionTool.isSelecting;
if (GUIUtility.hotControl == k_EdgeID || EdgeDragModifiersActive() && handleEvent)
HandleEdgeDragging(m_MouseClosestEdge);
else if (GUIUtility.hotControl == k_CreatorID || (currentEvent.modifiers == EventModifiers.None && handleEvent))
HandlePointInsertToEdge(m_MouseClosestEdge, m_MouseClosestEdgeDist);
}
}
if (guiUtility.hotControl != k_CreatorID && m_NewPointIndex != -1)
{
m_NewPointDragFinished = true;
guiUtility.keyboardControl = 0;
m_NewPointIndex = -1;
}
if (guiUtility.hotControl != k_EdgeID && m_ActiveEdge != -1)
{
m_ActiveEdge = -1;
}
}
public void Tangents()
{
if (activePoint < 0 || m_Selection.Count > 1 || GetTangentMode(activePoint) == TangentMode.Linear)
return;
var evt = eventSystem.current;
var p = this.GetPointPosition(activePoint);
var lt = this.GetPointLTangent(activePoint);
var rt = this.GetPointRTangent(activePoint);
var isHot = (guiUtility.hotControl == k_RightTangentID || guiUtility.hotControl == k_LeftTangentID);
var allZero = lt.sqrMagnitude == 0 && rt.sqrMagnitude == 0;
if (isHot || !allZero)
{
var m = this.GetTangentMode(activePoint);
var mouseDown = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseDown || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseDown;
var mouseUp = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseUp || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseUp;
var nlt = DoTangent(p, p + lt, k_LeftTangentID, activePoint, k_TangentColor);
var nrt = DoTangent(p, p + rt, k_RightTangentID, activePoint, GetTangentMode(activePoint) == TangentMode.Broken ? k_TangentColorAlternative : k_TangentColor);
var changed = (nlt != lt || nrt != rt);
allZero = nlt.sqrMagnitude == 0 && nrt.sqrMagnitude == 0;
// if indeed we are dragging one of the handles and we just shift+mousedown, toggle the point. this happens even without change!
if (isHot && mouseDown)
{
var nm = ((int)m + 1) % 3;
m = (TangentMode)nm;
//m = m == PointMode.Continuous ? PointMode.Broken : PointMode.Continuous;
this.SetTangentMode(activePoint, m);
}
// make it broken when both tangents are zero
if (mouseUp && allZero)
{
this.SetTangentMode(activePoint, TangentMode.Linear);
changed = true;
}
// only do something when it's changed
if (changed)
{
RecordUndo();
this.SetPointLTangent(activePoint, nlt);
this.SetPointRTangent(activePoint, nrt);
RefreshTangents(activePoint, guiUtility.hotControl == k_RightTangentID);
Repaint();
}
}
}
public void Points()
{
var wantsDelete =
(UnityEngine.Event.current.type == EventType.ExecuteCommand || UnityEngine.Event.current.type == EventType.ValidateCommand)
&& (UnityEngine.Event.current.commandName == EventCommandNames.SoftDelete || UnityEngine.Event.current.commandName == EventCommandNames.Delete);
for (int i = 0; i < this.GetPointsCount(); i++)
{
if (i == m_NewPointIndex)
continue;
var p0 = this.GetPointPosition(i);
var id = guiUtility.GetControlID(5353, FocusType.Keyboard);
var mouseDown = currentEvent.GetTypeForControl(id) == EventType.MouseDown;
var mouseUp = currentEvent.GetTypeForControl(id) == EventType.MouseUp;
EditorGUI.BeginChangeCheck();
if (currentEvent.type == EventType.Repaint)
{
ColorEnum c = GetColorForPoint(i, id);
handleOutlineColor = k_OutlineColor[(int)c];
handleFillColor = k_FillColor[(int)c];
}
var np = p0;
var hotControlBefore = guiUtility.hotControl;
if (!currentEvent.alt || guiUtility.hotControl == id)
np = DoSlider(id, p0, Vector3.up, Vector3.right, GetHandleSizeForPoint(i), GetCapForPoint(i));
else if (currentEvent.type == EventType.Repaint)
GetCapForPoint(i)(id, p0, Quaternion.LookRotation(Vector3.forward, Vector3.up), GetHandleSizeForPoint(i), currentEvent.type);
var hotcontrolAfter = guiUtility.hotControl;
if (mouseUp && hotControlBefore == id && hotcontrolAfter == 0 && (currentEvent.mousePosition == m_MousePositionLastMouseDown) && !currentEvent.shift)
HandlePointClick(i);
if (EditorGUI.EndChangeCheck())
{
RecordUndo();
np = Snap(np);
MoveSelections(np - p0);
}
if (guiUtility.hotControl == id && mouseDown && !m_Selection.Contains(i))
{
SelectPoint(i, currentEvent.shift ? SelectionType.Additive : SelectionType.Normal);
Repaint();
}
if (m_NewPointDragFinished && activePoint == i && id != -1)
{
guiUtility.keyboardControl = id;
m_NewPointDragFinished = false;
}
}
if (wantsDelete)
{
if (currentEvent.type == EventType.ValidateCommand)
{
currentEvent.Use();
}
else if (currentEvent.type == EventType.ExecuteCommand)
{
RecordUndo();
DeleteSelections();
currentEvent.Use();
}
}
}
public void HandlePointInsertToEdge(int closestEdge, float closestEdgeDist)
{
var pointCreatorIsHot = GUIUtility.hotControl == k_CreatorID;
Vector3 position = pointCreatorIsHot ? GetPointPosition(m_NewPointIndex) : FindClosestPointOnEdge(closestEdge, ScreenToLocal(currentEvent.mousePosition), 100);
EditorGUI.BeginChangeCheck();
handleFillColor = k_FillColor[(int)ColorEnum.ESelectedHovered];
handleOutlineColor = k_OutlineColor[(int)ColorEnum.ESelectedHovered];
if (!pointCreatorIsHot)
{
handleFillColor = handleFillColor.AlphaMultiplied(0.5f);
handleOutlineColor = handleOutlineColor.AlphaMultiplied(0.5f);
}
int hotControlBefore = GUIUtility.hotControl;
var newPosition = DoSlider(k_CreatorID, position, Vector3.up, Vector3.right, GetHandleSizeForPoint(closestEdge), RectCap);
if (hotControlBefore != k_CreatorID && GUIUtility.hotControl == k_CreatorID)
{
RecordUndo();
m_NewPointIndex = NextIndex(closestEdge, GetPointsCount());
InsertPointAt(m_NewPointIndex, newPosition);
SelectPoint(m_NewPointIndex, SelectionType.Normal);
}
else if (EditorGUI.EndChangeCheck())
{
RecordUndo();
newPosition = Snap(newPosition);
MoveSelections(newPosition - position);
}
}
private void HandleEdgeDragging(int closestEdge)
{
switch (currentEvent.type)
{
case EventType.MouseDown:
m_ActiveEdge = closestEdge;
m_EdgeDragStartP0 = GetPointPosition(m_ActiveEdge);
m_EdgeDragStartP1 = GetPointPosition(NextIndex(m_ActiveEdge, GetPointsCount()));
if (currentEvent.shift)
{
RecordUndo();
InsertPointAt(m_ActiveEdge + 1, m_EdgeDragStartP0);
InsertPointAt(m_ActiveEdge + 2, m_EdgeDragStartP1);
m_ActiveEdge++;
}
m_EdgeDragStartMousePosition = ScreenToLocal(currentEvent.mousePosition);
GUIUtility.hotControl = k_EdgeID;
currentEvent.Use();
break;
case EventType.MouseDrag:
// This can happen when MouseDown event happen when dragging a point instead of line
if (GUIUtility.hotControl == k_EdgeID)
{
RecordUndo();
Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition);
Vector3 delta = mousePos - m_EdgeDragStartMousePosition;
Vector3 position = GetPointPosition(m_ActiveEdge);
Vector3 newPosition = m_EdgeDragStartP0 + delta;
newPosition = Snap(newPosition);
Vector3 snappedDelta = newPosition - position;
var i0 = m_ActiveEdge;
var i1 = NextIndex(m_ActiveEdge, GetPointsCount());
SetPointPosition(m_ActiveEdge, GetPointPosition(i0) + snappedDelta);
SetPointPosition(i1, GetPointPosition(i1) + snappedDelta);
currentEvent.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == k_EdgeID)
{
m_ActiveEdge = -1;
GUIUtility.hotControl = 0;
currentEvent.Use();
}
break;
}
}
Vector3 DoTangent(Vector3 p0, Vector3 t0, int cid, int pointIndex, Color color)
{
var handleSize = GetHandleSizeForPoint(pointIndex);
var tangentSize = GetTangentSizeForPoint(pointIndex);
handles.color = color;
float distance = HandleUtility.DistanceToCircle(t0, tangentSize);
if (lineTexture != null)
handles.DrawAAPolyLine(lineTexture, new Vector3[] {p0, t0});
else
handles.DrawLine(p0, t0);
handleOutlineColor = distance > 0f ? color : k_OutlineColor[(int)ColorEnum.ESelectedHovered];
handleFillColor = color;
var delta = DoSlider(cid, t0, Vector3.up, Vector3.right, tangentSize, GetCapForTangent(pointIndex)) - p0;
return delta.magnitude < handleSize ? Vector3.zero : delta;
}
public void HandlePointClick(int pointIndex)
{
if (m_Selection.Count > 1)
{
m_Selection.SelectPoint(pointIndex, SelectionType.Normal);
}
else if (!currentEvent.control && !currentEvent.shift && m_ActivePointOnLastMouseDown == activePoint)
{
OnPointClick(pointIndex);
}
}
public void CycleTangentMode()
{
TangentMode oldMode = GetTangentMode(activePoint);
TangentMode newMode = GetNextTangentMode(oldMode);
SetTangentMode(activePoint, newMode);
RefreshTangentsAfterModeChange(activePoint, oldMode, newMode);
}
public static TangentMode GetNextTangentMode(TangentMode current)
{
return (TangentMode)((((int)current) + 1) % Enum.GetValues(typeof(TangentMode)).Length);
}
public void RefreshTangentsAfterModeChange(int pointIndex, TangentMode oldMode, TangentMode newMode)
{
if (oldMode != TangentMode.Linear && newMode == TangentMode.Linear)
{
SetPointLTangent(pointIndex, Vector3.zero);
SetPointRTangent(pointIndex, Vector3.zero);
}
if (newMode == TangentMode.Continuous)
{
if (oldMode == TangentMode.Broken)
{
SetPointRTangent(pointIndex, GetPointLTangent(pointIndex) * -1f);
}
if (oldMode == TangentMode.Linear)
{
FromAllZeroToTangents(pointIndex);
}
}
}
private ColorEnum GetColorForPoint(int pointIndex, int handleID)
{
bool hovered = MouseDistanceToPoint(pointIndex) <= 0f;
bool selected = m_Selection.Contains(pointIndex);
if (hovered && selected || GUIUtility.hotControl == handleID)
return ColorEnum.ESelectedHovered;
if (hovered)
return ColorEnum.EUnselectedHovered;
if (selected)
return ColorEnum.ESelected;
return ColorEnum.EUnselected;
}
private void FromAllZeroToTangents(int pointIndex)
{
Vector3 p0 = GetPointPosition(pointIndex);
int prevPointIndex = pointIndex > 0 ? pointIndex - 1 : GetPointsCount() - 1;
Vector3 lt = (GetPointPosition(prevPointIndex) - p0) * .33f;
Vector3 rt = -lt;
const float maxScreenLen = 100f;
float ltScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + lt)).magnitude;
float rtScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + rt)).magnitude;
lt *= Mathf.Min(maxScreenLen / ltScreenLen, 1f);
rt *= Mathf.Min(maxScreenLen / rtScreenLen, 1f);
this.SetPointLTangent(pointIndex, lt);
this.SetPointRTangent(pointIndex, rt);
}
private Handles.CapFunction GetCapForTangent(int index)
{
if (GetTangentMode(index) == TangentMode.Continuous)
return CircleCap;
return DiamondCap;
}
private DistanceToControl GetDistanceFuncForTangent(int index)
{
if (GetTangentMode(index) == TangentMode.Continuous)
return DistanceToCircle();
return HandleUtility.DistanceToDiamond;
}
private Handles.CapFunction GetCapForPoint(int index)
{
switch (GetTangentMode(index))
{
case TangentMode.Broken:
return DiamondCap;
case TangentMode.Continuous:
return CircleCap;
case TangentMode.Linear:
return RectCap;
}
return DiamondCap;
}
private static float DistanceToCircleInternal(Vector3 position, Quaternion rotation, float size)
{
return HandleUtility.DistanceToCircle(position, size);
}
private float GetHandleSizeForPoint(int index)
{
return Camera.current != null ? HandleUtility.GetHandleSize(GetPointPosition(index)) * 0.075f : GetHandleSize();
}
private float GetTangentSizeForPoint(int index)
{
return GetHandleSizeForPoint(index) * 0.8f;
}
private void RefreshTangents(int index, bool rightIsActive)
{
var m = GetTangentMode(index);
var lt = GetPointLTangent(index);
var rt = GetPointRTangent(index);
// mirror the change on the other tangent for continuous
if (m == TangentMode.Continuous)
{
if (rightIsActive)
{
lt = -rt;
var mag = lt.magnitude;
lt = lt.normalized * mag;
}
else
{
rt = -lt;
var mag = rt.magnitude;
rt = rt.normalized * mag;
}
}
this.SetPointLTangent(activePoint, lt);
this.SetPointRTangent(activePoint, rt);
}
private void StoreMouseDownState()
{
m_MousePositionLastMouseDown = currentEvent.mousePosition;
m_ActivePointOnLastMouseDown = activePoint;
}
private void DelayedResetIfNecessary()
{
if (m_DelayedReset)
{
guiUtility.hotControl = 0;
guiUtility.keyboardControl = 0;
m_Selection.Clear();
activePoint = -1;
m_DelayedReset = false;
}
}
public Vector3 FindClosestPointOnEdge(int edgeIndex, Vector3 position, int iterations)
{
float step = 1f / iterations;
float closestDistance = float.MaxValue;
float closestDistanceIndex = edgeIndex;
for (float a = 0f; a <= 1f; a += step)
{
Vector3 pos = GetPositionByIndex(edgeIndex + a);
float distance = (position - pos).sqrMagnitude;
if (distance < closestDistance)
{
closestDistance = distance;
closestDistanceIndex = edgeIndex + a;
}
}
return GetPositionByIndex(closestDistanceIndex);
}
private Vector3 GetPositionByIndex(float index)
{
int indexA = Mathf.FloorToInt(index);
int indexB = NextIndex(indexA, GetPointsCount());
float subPosition = index - indexA;
return GetPoint(GetPointPosition(indexA), GetPointPosition(indexB), GetPointRTangent(indexA), GetPointLTangent(indexB), subPosition);
}
private static Vector3 GetPoint(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float t)
{
t = Mathf.Clamp01(t);
float a = 1f - t;
return a * a * a * startPosition + 3f * a * a * t * (startPosition + startTangent) + 3f * a * t * t * (endPosition + endTangent) + t * t * t * endPosition;
}
private int FindClosestPointToMouse()
{
Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition);
return FindClosestPointIndex(mousePos);
}
private float MouseDistanceToClosestTangent()
{
if (activePoint < 0)
return float.MaxValue;
var lt = GetPointLTangent(activePoint);
var rt = GetPointRTangent(activePoint);
if (lt.sqrMagnitude == 0f && rt.sqrMagnitude == 0f)
return float.MaxValue;
var p = GetPointPosition(activePoint);
var tangentSize = GetTangentSizeForPoint(activePoint);
return Mathf.Min(
HandleUtility.DistanceToRectangle(p + lt, Quaternion.identity, tangentSize),
HandleUtility.DistanceToRectangle(p + rt, Quaternion.identity, tangentSize)
);
}
private int FindClosestPointIndex(Vector3 position)
{
float closestDistance = float.MaxValue;
int closestIndex = -1;
for (int i = 0; i < this.GetPointsCount(); i++)
{
var p0 = this.GetPointPosition(i);
var distance = (p0 - position).sqrMagnitude;
if (distance < closestDistance)
{
closestIndex = i;
closestDistance = distance;
}
}
return closestIndex;
}
private DistanceToControl GetDistanceFuncForPoint(int index)
{
switch (GetTangentMode(index))
{
case TangentMode.Broken:
return DistanceToDiamond();
case TangentMode.Continuous:
return DistanceToCircle();
case TangentMode.Linear:
return DistanceToRectangle();
}
return DistanceToRectangle();
}
private float MouseDistanceToPoint(int index)
{
switch (GetTangentMode(index))
{
case TangentMode.Broken:
return HandleUtility.DistanceToDiamond(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index));
case TangentMode.Linear:
return HandleUtility.DistanceToRectangle(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index));
case TangentMode.Continuous:
return HandleUtility.DistanceToCircle(GetPointPosition(index), GetHandleSizeForPoint(index));
}
return float.MaxValue;
}
private bool EdgeDragModifiersActive()
{
return currentEvent.modifiers == EventModifiers.Control;
}
private static Vector3 DoSlider(int id, Vector3 position, Vector3 slide1, Vector3 slide2, float s, Handles.CapFunction cap)
{
return Slider2D.Do(id, position, Vector3.zero, Vector3.Cross(slide1, slide2), slide1, slide2, s, cap, Vector2.zero, false);
}
public void RectCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
{
if (eventType == EventType.Layout || eventType == EventType.MouseMove)
{
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
}
else if (eventType == EventType.Repaint)
{
Vector3 normal = handles.matrix.GetColumn(2);
Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized;
Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp);
Vector3 sideways = localRotation * Vector3.right * size;
Vector3 up = localRotation * Vector3.up * size;
List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
list.Add(position + sideways + up);
list.Add(position + sideways - up);
list.Add(position - sideways - up);
list.Add(position - sideways - up);
list.Add(position - sideways + up);
list.Add(position + sideways + up);
list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
list.Add(position + sideways + up);
list.Add(position + sideways - up);
list.Add(position + sideways - up);
list.Add(position - sideways - up);
list.Add(position - sideways - up);
list.Add(position - sideways + up);
list.Add(position - sideways + up);
list.Add(position + sideways + up);
}
}
public void CircleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
{
if (eventType == EventType.Layout || eventType == EventType.MouseMove)
{
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
}
else if (eventType == EventType.Repaint)
{
Vector3 forward = handleMatrixrotation * rotation * Vector3.forward;
Vector3 tangent = Vector3.Cross(forward, Vector3.up);
if (tangent.sqrMagnitude < .001f)
tangent = Vector3.Cross(forward, Vector3.right);
Vector3[] points = new Vector3[60];
handles.SetDiscSectionPoints(points, position, forward, tangent, 360f, size);
List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
for (int i = 1; i < points.Length; i++)
{
list.Add(position);
list.Add(points[i]);
list.Add(points[i - 1]);
}
list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
for (int i = 0; i < points.Length - 1; i++)
{
list.Add(points[i]);
list.Add(points[i + 1]);
}
}
}
public void DiamondCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
{
if (eventType == EventType.Layout || eventType == EventType.MouseMove)
{
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f));
}
else if (eventType == EventType.Repaint)
{
Vector3 normal = handles.matrix.GetColumn(2);
Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized;
Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp);
Vector3 sideways = localRotation * Vector3.right * size * 1.25f;
Vector3 up = localRotation * Vector3.up * size * 1.25f;
List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES));
list.Add(position - up);
list.Add(position + sideways);
list.Add(position - sideways);
list.Add(position - sideways);
list.Add(position + up);
list.Add(position + sideways);
list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES));
list.Add(position + sideways);
list.Add(position - up);
list.Add(position - up);
list.Add(position - sideways);
list.Add(position - sideways);
list.Add(position + up);
list.Add(position + up);
list.Add(position + sideways);
}
}
private static int NextIndex(int index, int total)
{
return mod(index + 1, total);
}
private static int PreviousIndex(int index, int total)
{
return mod(index - 1, total);
}
private static int mod(int x, int m)
{
int r = x % m;
return r < 0 ? r + m : r;
}
private static Vector3 ProjectPointOnPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 point)
{
planeNormal.Normalize();
var distance = -Vector3.Dot(planeNormal.normalized, (point - planePoint));
return point + planeNormal * distance;
}
public void RegisterToShapeEditor(ShapeEditor se)
{
++m_ShapeEditorRegisteredTo;
se.m_ShapeEditorListeners.Add(this);
}
public void UnregisterFromShapeEditor(ShapeEditor se)
{
--m_ShapeEditorRegisteredTo;
se.m_ShapeEditorListeners.Remove(this);
}
private void OnShapeEditorUpdateDone()
{
// When all the ShapeEditor we are interested in
// has completed their update, we reset our internal values
++m_ShapeEditorUpdateDone;
if (m_ShapeEditorUpdateDone >= m_ShapeEditorRegisteredTo)
{
m_ShapeEditorUpdateDone = 0;
m_MouseClosestEdge = -1;
m_MouseClosestEdgeDist = float.MaxValue;
m_EdgePoints = null;
}
}
private void ClearSelectedPoints()
{
selectedPoints.Clear();
activePoint = -1;
}
private void SelectPointsInRect(Rect r, SelectionType st)
{
var localRect = EditorGUIExt.FromToRect(ScreenToLocal(r.min), ScreenToLocal(r.max));
m_Selection.RectSelect(localRect, st);
}
private void DeleteSelections()
{
foreach (var se in m_ShapeEditorListeners)
se.m_Selection.DeleteSelection();
m_Selection.DeleteSelection();
}
private void MoveSelections(Vector2 distance)
{
foreach (var se in m_ShapeEditorListeners)
se.m_Selection.MoveSelection(distance);
m_Selection.MoveSelection(distance);
}
private void SelectPoint(int index, SelectionType st)
{
if (st == SelectionType.Normal)
{
foreach (var se in m_ShapeEditorListeners)
se.ClearSelectedPoints();
}
m_Selection.SelectPoint(index, st);
}
}
}