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 GetPointPosition = i => Vector3.zero; public Action SetPointPosition = (i, p) => {}; public Func GetPointLTangent = i => Vector3.zero; public Action SetPointLTangent = (i, p) => {}; public Func GetPointRTangent = i => Vector3.zero; public Action SetPointRTangent = (i, p) => {}; public Func GetTangentMode = i => TangentMode.Linear; public Action SetTangentMode = (i, m) => {}; public Action InsertPointAt = (i, p) => {}; public Action RemovePointAt = i => {}; public Func GetPointsCount = () => 0; // --- Transforms public Func ScreenToLocal = i => i; public Func LocalToScreen = i => i; public Func LocalToWorldMatrix = () => Matrix4x4.identity; // --- Distance functions public Func DistanceToRectangle = () => HandleUtility.DistanceToRectangle; public Func DistanceToDiamond = () => HandleUtility.DistanceToDiamond; public Func DistanceToCircle = () => DistanceToCircleInternal; // --- Other public Action Repaint = () => {}; public Action RecordUndo = () => {}; public Func Snap = i => i; public Action Frame = b => {}; public Action OnPointClick = i => {}; public Func OpenEnded = () => false; public Func GetHandleSize = () => 5f; // --- END public ITexture2D lineTexture { get; set; } public int activePoint { get; set; } public HashSet 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 m_ShapeEditorListeners = new HashSet(); 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> 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>(); } } 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 GetDrawBatchList(DrawBatchDataKey key) { List data = null; if (!m_DrawBatch.ContainsKey(key)) { m_DrawBatch.Add(key, new List()); } 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 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 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 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); } } }