A game about forced loneliness, made by TACStudios
at master 1112 lines 45 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEditorInternal; 6using UnityEngine.U2D.Sprites; 7 8namespace UnityEditor.U2D.Sprites 9{ 10 internal interface IShapeEditorFactory 11 { 12 ShapeEditor CreateShapeEditor(); 13 } 14 15 internal class ShapeEditorFactory : IShapeEditorFactory 16 { 17 public ShapeEditor CreateShapeEditor() 18 { 19 return new ShapeEditor(new GUIUtilitySystem(), new EventSystem()); 20 } 21 } 22 23 internal class ShapeEditor 24 { 25 public delegate float DistanceToControl(Vector3 pos, Quaternion rotation, float handleSize); 26 27 internal enum SelectionType { Normal, Additive, Subtractive } 28 internal enum Tool { Edit, Create, Break } 29 internal enum TangentMode { Linear, Continuous, Broken } 30 31 // --- To use this class in your editor, you need to implement these: (see ShapeEditorTests for example) 32 // --- Data 33 public Func<int, Vector3> GetPointPosition = i => Vector3.zero; 34 public Action<int, Vector3> SetPointPosition = (i, p) => {}; 35 public Func<int, Vector3> GetPointLTangent = i => Vector3.zero; 36 public Action<int, Vector3> SetPointLTangent = (i, p) => {}; 37 public Func<int, Vector3> GetPointRTangent = i => Vector3.zero; 38 public Action<int, Vector3> SetPointRTangent = (i, p) => {}; 39 public Func<int, TangentMode> GetTangentMode = i => TangentMode.Linear; 40 public Action<int, TangentMode> SetTangentMode = (i, m) => {}; 41 public Action<int, Vector3> InsertPointAt = (i, p) => {}; 42 public Action<int> RemovePointAt = i => {}; 43 public Func<int> GetPointsCount = () => 0; 44 // --- Transforms 45 public Func<Vector2, Vector3> ScreenToLocal = i => i; 46 public Func<Vector3, Vector2> LocalToScreen = i => i; 47 public Func<Matrix4x4> LocalToWorldMatrix = () => Matrix4x4.identity; 48 // --- Distance functions 49 public Func<DistanceToControl> DistanceToRectangle = () => HandleUtility.DistanceToRectangle; 50 public Func<DistanceToControl> DistanceToDiamond = () => HandleUtility.DistanceToDiamond; 51 public Func<DistanceToControl> DistanceToCircle = () => DistanceToCircleInternal; 52 // --- Other 53 public Action Repaint = () => {}; 54 public Action RecordUndo = () => {}; 55 public Func<Vector3, Vector3> Snap = i => i; 56 public Action<Bounds> Frame = b => {}; 57 public Action<int> OnPointClick = i => {}; 58 public Func<bool> OpenEnded = () => false; 59 public Func<float> GetHandleSize = () => 5f; 60 // --- END 61 62 public ITexture2D lineTexture { get; set; } 63 public int activePoint { get; set; } 64 public HashSet<int> selectedPoints { get { return m_Selection.indices; } } 65 public bool inEditMode { get; set; } 66 public int activeEdge { get { return m_ActiveEdge; } set { m_ActiveEdge = value; } } 67 // 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 68 public bool delayedReset { set { m_DelayedReset = value; } } 69 70 private ShapeEditorSelection m_Selection; 71 private Vector2 m_MousePositionLastMouseDown; 72 private int m_ActivePointOnLastMouseDown = -1; 73 private int m_NewPointIndex = -1; 74 private Vector3 m_EdgeDragStartMousePosition; 75 private Vector3 m_EdgeDragStartP0; 76 private Vector3 m_EdgeDragStartP1; 77 private bool m_NewPointDragFinished; 78 private int m_ActiveEdge = -1; 79 private bool m_DelayedReset = false; 80 private HashSet<ShapeEditor> m_ShapeEditorListeners = new HashSet<ShapeEditor>(); 81 private ShapeEditorRectSelectionTool m_RectSelectionTool; 82 83 private int m_MouseClosestEdge = -1; 84 private float m_MouseClosestEdgeDist = float.MaxValue; 85 private int m_ShapeEditorRegisteredTo = 0; 86 private int m_ShapeEditorUpdateDone = 0; 87 88 private static Color handleOutlineColor { get; set; } 89 private static Color handleFillColor { get; set; } 90 private Quaternion handleMatrixrotation { get { return Quaternion.LookRotation(handles.matrix.GetColumn(2), handles.matrix.GetColumn(1)); } } 91 92 private IGUIUtility guiUtility { get; set; } 93 private IEventSystem eventSystem { get; set; } 94 private IEvent currentEvent { get; set; } 95 private IGL glSystem { get; set; } 96 private IHandles handles { get; set; } 97 98 private Dictionary<DrawBatchDataKey, List<Vector3>> m_DrawBatch; 99 private Vector3[][] m_EdgePoints; 100 101 enum ColorEnum 102 { 103 EUnselected, 104 EUnselectedHovered, 105 ESelected, 106 ESelectedHovered 107 } 108 109 private static readonly Color[] k_OutlineColor = new[] 110 { 111 Color.gray, 112 Color.white, 113 new Color(34f / 255f, 171f / 255f, 1f), // #22abff 114 Color.white 115 }; 116 117 static readonly Color[] k_FillColor = new[] 118 { 119 Color.white, 120 new Color(131f / 255f, 220f / 255f, 1f), // #83dcff 121 new Color(34f / 255f, 171f / 255f, 1f), // #22abff 122 new Color(34f / 255f, 171f / 255f, 1f) // #22abff 123 }; 124 private static readonly Color k_TangentColor = new Color(34f / 255f, 171f / 255f, 1f); // #22abff 125 private static readonly Color k_TangentColorAlternative = new Color(131f / 255f, 220f / 255f, 1f); // #83dcff 126 private const float k_EdgeHoverDistance = 9f; 127 private const float k_EdgeWidth = 2f; 128 private const float k_ActiveEdgeWidth = 6f; 129 private const float k_MinExistingPointDistanceForInsert = 20f; 130 private readonly int k_CreatorID; 131 private readonly int k_EdgeID; 132 private readonly int k_RightTangentID; 133 private readonly int k_LeftTangentID; 134 private const int k_BezierPatch = 40; 135 136 class DrawBatchDataKey 137 { 138 public Color color { get; private set; } 139 public int glMode { get; private set; } 140 private int m_Hash; 141 142 public DrawBatchDataKey(Color c , int mode) 143 { 144 color = c; 145 glMode = mode; 146 m_Hash = glMode ^ (color.GetHashCode() << 2); 147 } 148 149 public override int GetHashCode() 150 { 151 return m_Hash; 152 } 153 154 public override bool Equals(object obj) 155 { 156 var key = obj as DrawBatchDataKey; 157 if (ReferenceEquals(key, null)) 158 return false; 159 return m_Hash == obj.GetHashCode(); 160 } 161 } 162 163 public ShapeEditor(IGUIUtility gu, IEventSystem es) 164 { 165 m_Selection = new ShapeEditorSelection(this); 166 guiUtility = gu; 167 eventSystem = es; 168 k_CreatorID = guiUtility.GetPermanentControlID(); 169 k_EdgeID = guiUtility.GetPermanentControlID(); 170 k_RightTangentID = guiUtility.GetPermanentControlID(); 171 k_LeftTangentID = guiUtility.GetPermanentControlID(); 172 glSystem = GLSystem.GetSystem(); 173 handles = HandlesSystem.GetSystem(); 174 } 175 176 public void SetRectSelectionTool(ShapeEditorRectSelectionTool sers) 177 { 178 if (m_RectSelectionTool != null) 179 { 180 m_RectSelectionTool.RectSelect -= SelectPointsInRect; 181 m_RectSelectionTool.ClearSelection -= ClearSelectedPoints; 182 } 183 m_RectSelectionTool = sers; 184 m_RectSelectionTool.RectSelect += SelectPointsInRect; 185 m_RectSelectionTool.ClearSelection += ClearSelectedPoints; 186 } 187 188 public void OnDisable() 189 { 190 m_RectSelectionTool.RectSelect -= SelectPointsInRect; 191 m_RectSelectionTool.ClearSelection -= ClearSelectedPoints; 192 m_RectSelectionTool = null; 193 } 194 195 private void PrepareDrawBatch() 196 { 197 if (currentEvent.type == EventType.Repaint) 198 { 199 m_DrawBatch = new Dictionary<DrawBatchDataKey, List<Vector3>>(); 200 } 201 } 202 203 private void DrawBatch() 204 { 205 if (currentEvent.type == EventType.Repaint) 206 { 207 HandleUtility.ApplyWireMaterial(); 208 glSystem.PushMatrix(); 209 glSystem.MultMatrix(handles.matrix); 210 foreach (var drawBatch in m_DrawBatch) 211 { 212 glSystem.Begin(drawBatch.Key.glMode); 213 glSystem.Color(drawBatch.Key.color); 214 foreach (var t in drawBatch.Value) 215 { 216 glSystem.Vertex(t); 217 } 218 glSystem.End(); 219 } 220 glSystem.PopMatrix(); 221 } 222 } 223 224 List<Vector3> GetDrawBatchList(DrawBatchDataKey key) 225 { 226 List<Vector3> data = null; 227 if (!m_DrawBatch.ContainsKey(key)) 228 { 229 m_DrawBatch.Add(key, new List<Vector3>()); 230 } 231 data = m_DrawBatch[key]; 232 return data; 233 } 234 235 public void OnGUI() 236 { 237 DelayedResetIfNecessary(); 238 currentEvent = eventSystem.current; 239 240 if (currentEvent.type == EventType.MouseDown) 241 StoreMouseDownState(); 242 243 var oldColor = handles.color; 244 var oldMatrix = handles.matrix; 245 handles.matrix = LocalToWorldMatrix(); 246 247 PrepareDrawBatch(); 248 249 Edges(); 250 251 if (inEditMode) 252 { 253 Framing(); 254 Tangents(); 255 Points(); 256 } 257 258 DrawBatch(); 259 260 handles.color = oldColor; 261 handles.matrix = oldMatrix; 262 263 OnShapeEditorUpdateDone(); 264 foreach (var se in m_ShapeEditorListeners) 265 se.OnShapeEditorUpdateDone(); 266 } 267 268 private void Framing() 269 { 270 if (currentEvent.commandName == EventCommandNames.FrameSelected && m_Selection.Count > 0) 271 { 272 switch (currentEvent.type) 273 { 274 case EventType.ExecuteCommand: 275 Bounds bounds = new Bounds(GetPointPosition(selectedPoints.First()), Vector3.zero); 276 277 foreach (var index in selectedPoints) 278 bounds.Encapsulate(GetPointPosition(index)); 279 280 Frame(bounds); 281 goto case EventType.ValidateCommand; 282 case EventType.ValidateCommand: 283 currentEvent.Use(); 284 break; 285 } 286 } 287 } 288 289 void PrepareEdgePointList() 290 { 291 if (m_EdgePoints == null) 292 { 293 var total = this.GetPointsCount(); 294 int loopCount = OpenEnded() ? total - 1 : total; 295 m_EdgePoints = new Vector3[loopCount][]; 296 int index = mod(total - 1, loopCount); 297 for (int loop = mod(index + 1, total); loop < total; index = loop, ++loop) 298 { 299 var position0 = this.GetPointPosition(index); 300 var position1 = this.GetPointPosition(loop); 301 if (GetTangentMode(index) == TangentMode.Linear && GetTangentMode(loop) == TangentMode.Linear) 302 { 303 m_EdgePoints[index] = new[] { position0, position1 }; 304 } 305 else 306 { 307 var tangent0 = GetPointRTangent(index) + position0; 308 var tangent1 = GetPointLTangent(loop) + position1; 309 m_EdgePoints[index] = handles.MakeBezierPoints(position0, position1, tangent0, tangent1, k_BezierPatch); 310 } 311 } 312 } 313 } 314 315 float DistancePointEdge(Vector3 point, Vector3[] edge) 316 { 317 float result = float.MaxValue; 318 int index = edge.Length - 1; 319 for (int nextIndex = 0; nextIndex < edge.Length; index = nextIndex, nextIndex++) 320 { 321 float dist = HandleUtility.DistancePointLine(point, edge[index], edge[nextIndex]); 322 if (dist < result) 323 result = dist; 324 } 325 return result; 326 } 327 328 private float GetMouseClosestEdgeDistance() 329 { 330 var mousePosition = ScreenToLocal(eventSystem.current.mousePosition); 331 var total = this.GetPointsCount(); 332 if (m_MouseClosestEdge == -1 && total > 0) 333 { 334 PrepareEdgePointList(); 335 m_MouseClosestEdgeDist = float.MaxValue; 336 337 int loopCount = OpenEnded() ? total - 1 : total; 338 for (int loop = 0; loop < loopCount; loop++) 339 { 340 var dist = DistancePointEdge(mousePosition, m_EdgePoints[loop]); 341 if (dist < m_MouseClosestEdgeDist) 342 { 343 m_MouseClosestEdge = loop; 344 m_MouseClosestEdgeDist = dist; 345 } 346 } 347 } 348 349 if (guiUtility.hotControl == k_CreatorID || guiUtility.hotControl == k_EdgeID) 350 return float.MinValue; 351 return m_MouseClosestEdgeDist; 352 } 353 354 public void Edges() 355 { 356 var otherClosestEdgeDistance = float.MaxValue; 357 if (m_ShapeEditorListeners.Count > 0) 358 otherClosestEdgeDistance = (from se in m_ShapeEditorListeners select se.GetMouseClosestEdgeDistance()).Max(); 359 float edgeDistance = GetMouseClosestEdgeDistance(); 360 bool closestEdgeHighlight = EdgeDragModifiersActive() && edgeDistance < k_EdgeHoverDistance && edgeDistance < otherClosestEdgeDistance; 361 if (currentEvent.type == EventType.Repaint) 362 { 363 Color handlesOldColor = handles.color; 364 PrepareEdgePointList(); 365 366 var total = this.GetPointsCount(); 367 int loopCount = OpenEnded() ? total - 1 : total; 368 369 for (int loop = 0; loop < loopCount; loop++) 370 { 371 Color edgeColor = loop == m_ActiveEdge ? Color.yellow : Color.white; 372 float edgeWidth = loop == m_ActiveEdge || (m_MouseClosestEdge == loop && closestEdgeHighlight) ? k_ActiveEdgeWidth : k_EdgeWidth; 373 374 handles.color = edgeColor; 375 handles.DrawAAPolyLine(lineTexture, edgeWidth, m_EdgePoints[loop]); 376 } 377 handles.color = handlesOldColor; 378 } 379 380 if (inEditMode) 381 { 382 if (otherClosestEdgeDistance > edgeDistance) 383 { 384 var farEnoughFromExistingPoints = MouseDistanceToPoint(FindClosestPointToMouse()) > k_MinExistingPointDistanceForInsert; 385 var farEnoughtFromActiveTangents = MouseDistanceToClosestTangent() > k_MinExistingPointDistanceForInsert; 386 var farEnoughFromExisting = farEnoughFromExistingPoints && farEnoughtFromActiveTangents; 387 var hoveringEdge = m_MouseClosestEdgeDist < k_EdgeHoverDistance; 388 var handleEvent = hoveringEdge && farEnoughFromExisting && !m_RectSelectionTool.isSelecting; 389 390 if (GUIUtility.hotControl == k_EdgeID || EdgeDragModifiersActive() && handleEvent) 391 HandleEdgeDragging(m_MouseClosestEdge); 392 else if (GUIUtility.hotControl == k_CreatorID || (currentEvent.modifiers == EventModifiers.None && handleEvent)) 393 HandlePointInsertToEdge(m_MouseClosestEdge, m_MouseClosestEdgeDist); 394 } 395 } 396 397 if (guiUtility.hotControl != k_CreatorID && m_NewPointIndex != -1) 398 { 399 m_NewPointDragFinished = true; 400 guiUtility.keyboardControl = 0; 401 m_NewPointIndex = -1; 402 } 403 if (guiUtility.hotControl != k_EdgeID && m_ActiveEdge != -1) 404 { 405 m_ActiveEdge = -1; 406 } 407 } 408 409 public void Tangents() 410 { 411 if (activePoint < 0 || m_Selection.Count > 1 || GetTangentMode(activePoint) == TangentMode.Linear) 412 return; 413 414 var evt = eventSystem.current; 415 var p = this.GetPointPosition(activePoint); 416 var lt = this.GetPointLTangent(activePoint); 417 var rt = this.GetPointRTangent(activePoint); 418 419 var isHot = (guiUtility.hotControl == k_RightTangentID || guiUtility.hotControl == k_LeftTangentID); 420 var allZero = lt.sqrMagnitude == 0 && rt.sqrMagnitude == 0; 421 422 if (isHot || !allZero) 423 { 424 var m = this.GetTangentMode(activePoint); 425 var mouseDown = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseDown || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseDown; 426 var mouseUp = evt.GetTypeForControl(k_RightTangentID) == EventType.MouseUp || evt.GetTypeForControl(k_LeftTangentID) == EventType.MouseUp; 427 428 var nlt = DoTangent(p, p + lt, k_LeftTangentID, activePoint, k_TangentColor); 429 var nrt = DoTangent(p, p + rt, k_RightTangentID, activePoint, GetTangentMode(activePoint) == TangentMode.Broken ? k_TangentColorAlternative : k_TangentColor); 430 431 var changed = (nlt != lt || nrt != rt); 432 allZero = nlt.sqrMagnitude == 0 && nrt.sqrMagnitude == 0; 433 434 // if indeed we are dragging one of the handles and we just shift+mousedown, toggle the point. this happens even without change! 435 if (isHot && mouseDown) 436 { 437 var nm = ((int)m + 1) % 3; 438 m = (TangentMode)nm; 439 //m = m == PointMode.Continuous ? PointMode.Broken : PointMode.Continuous; 440 this.SetTangentMode(activePoint, m); 441 } 442 443 // make it broken when both tangents are zero 444 if (mouseUp && allZero) 445 { 446 this.SetTangentMode(activePoint, TangentMode.Linear); 447 changed = true; 448 } 449 450 // only do something when it's changed 451 if (changed) 452 { 453 RecordUndo(); 454 this.SetPointLTangent(activePoint, nlt); 455 this.SetPointRTangent(activePoint, nrt); 456 RefreshTangents(activePoint, guiUtility.hotControl == k_RightTangentID); 457 Repaint(); 458 } 459 } 460 } 461 462 public void Points() 463 { 464 var wantsDelete = 465 (UnityEngine.Event.current.type == EventType.ExecuteCommand || UnityEngine.Event.current.type == EventType.ValidateCommand) 466 && (UnityEngine.Event.current.commandName == EventCommandNames.SoftDelete || UnityEngine.Event.current.commandName == EventCommandNames.Delete); 467 for (int i = 0; i < this.GetPointsCount(); i++) 468 { 469 if (i == m_NewPointIndex) 470 continue; 471 472 var p0 = this.GetPointPosition(i); 473 var id = guiUtility.GetControlID(5353, FocusType.Keyboard); 474 var mouseDown = currentEvent.GetTypeForControl(id) == EventType.MouseDown; 475 var mouseUp = currentEvent.GetTypeForControl(id) == EventType.MouseUp; 476 477 EditorGUI.BeginChangeCheck(); 478 479 if (currentEvent.type == EventType.Repaint) 480 { 481 ColorEnum c = GetColorForPoint(i, id); 482 handleOutlineColor = k_OutlineColor[(int)c]; 483 handleFillColor = k_FillColor[(int)c]; 484 } 485 486 var np = p0; 487 var hotControlBefore = guiUtility.hotControl; 488 489 if (!currentEvent.alt || guiUtility.hotControl == id) 490 np = DoSlider(id, p0, Vector3.up, Vector3.right, GetHandleSizeForPoint(i), GetCapForPoint(i)); 491 else if (currentEvent.type == EventType.Repaint) 492 GetCapForPoint(i)(id, p0, Quaternion.LookRotation(Vector3.forward, Vector3.up), GetHandleSizeForPoint(i), currentEvent.type); 493 494 var hotcontrolAfter = guiUtility.hotControl; 495 496 if (mouseUp && hotControlBefore == id && hotcontrolAfter == 0 && (currentEvent.mousePosition == m_MousePositionLastMouseDown) && !currentEvent.shift) 497 HandlePointClick(i); 498 499 if (EditorGUI.EndChangeCheck()) 500 { 501 RecordUndo(); 502 np = Snap(np); 503 MoveSelections(np - p0); 504 } 505 506 if (guiUtility.hotControl == id && mouseDown && !m_Selection.Contains(i)) 507 { 508 SelectPoint(i, currentEvent.shift ? SelectionType.Additive : SelectionType.Normal); 509 Repaint(); 510 } 511 512 if (m_NewPointDragFinished && activePoint == i && id != -1) 513 { 514 guiUtility.keyboardControl = id; 515 m_NewPointDragFinished = false; 516 } 517 } 518 519 if (wantsDelete) 520 { 521 if (currentEvent.type == EventType.ValidateCommand) 522 { 523 currentEvent.Use(); 524 } 525 else if (currentEvent.type == EventType.ExecuteCommand) 526 { 527 RecordUndo(); 528 DeleteSelections(); 529 currentEvent.Use(); 530 } 531 } 532 } 533 534 public void HandlePointInsertToEdge(int closestEdge, float closestEdgeDist) 535 { 536 var pointCreatorIsHot = GUIUtility.hotControl == k_CreatorID; 537 538 Vector3 position = pointCreatorIsHot ? GetPointPosition(m_NewPointIndex) : FindClosestPointOnEdge(closestEdge, ScreenToLocal(currentEvent.mousePosition), 100); 539 540 EditorGUI.BeginChangeCheck(); 541 542 handleFillColor = k_FillColor[(int)ColorEnum.ESelectedHovered]; 543 handleOutlineColor = k_OutlineColor[(int)ColorEnum.ESelectedHovered]; 544 545 if (!pointCreatorIsHot) 546 { 547 handleFillColor = handleFillColor.AlphaMultiplied(0.5f); 548 handleOutlineColor = handleOutlineColor.AlphaMultiplied(0.5f); 549 } 550 551 int hotControlBefore = GUIUtility.hotControl; 552 var newPosition = DoSlider(k_CreatorID, position, Vector3.up, Vector3.right, GetHandleSizeForPoint(closestEdge), RectCap); 553 if (hotControlBefore != k_CreatorID && GUIUtility.hotControl == k_CreatorID) 554 { 555 RecordUndo(); 556 m_NewPointIndex = NextIndex(closestEdge, GetPointsCount()); 557 InsertPointAt(m_NewPointIndex, newPosition); 558 SelectPoint(m_NewPointIndex, SelectionType.Normal); 559 } 560 else if (EditorGUI.EndChangeCheck()) 561 { 562 RecordUndo(); 563 newPosition = Snap(newPosition); 564 MoveSelections(newPosition - position); 565 } 566 } 567 568 private void HandleEdgeDragging(int closestEdge) 569 { 570 switch (currentEvent.type) 571 { 572 case EventType.MouseDown: 573 m_ActiveEdge = closestEdge; 574 m_EdgeDragStartP0 = GetPointPosition(m_ActiveEdge); 575 m_EdgeDragStartP1 = GetPointPosition(NextIndex(m_ActiveEdge, GetPointsCount())); 576 if (currentEvent.shift) 577 { 578 RecordUndo(); 579 InsertPointAt(m_ActiveEdge + 1, m_EdgeDragStartP0); 580 InsertPointAt(m_ActiveEdge + 2, m_EdgeDragStartP1); 581 m_ActiveEdge++; 582 } 583 m_EdgeDragStartMousePosition = ScreenToLocal(currentEvent.mousePosition); 584 GUIUtility.hotControl = k_EdgeID; 585 currentEvent.Use(); 586 break; 587 case EventType.MouseDrag: 588 // This can happen when MouseDown event happen when dragging a point instead of line 589 if (GUIUtility.hotControl == k_EdgeID) 590 { 591 RecordUndo(); 592 Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition); 593 Vector3 delta = mousePos - m_EdgeDragStartMousePosition; 594 595 Vector3 position = GetPointPosition(m_ActiveEdge); 596 Vector3 newPosition = m_EdgeDragStartP0 + delta; 597 598 newPosition = Snap(newPosition); 599 600 Vector3 snappedDelta = newPosition - position; 601 var i0 = m_ActiveEdge; 602 var i1 = NextIndex(m_ActiveEdge, GetPointsCount()); 603 SetPointPosition(m_ActiveEdge, GetPointPosition(i0) + snappedDelta); 604 SetPointPosition(i1, GetPointPosition(i1) + snappedDelta); 605 currentEvent.Use(); 606 } 607 break; 608 case EventType.MouseUp: 609 if (GUIUtility.hotControl == k_EdgeID) 610 { 611 m_ActiveEdge = -1; 612 GUIUtility.hotControl = 0; 613 currentEvent.Use(); 614 } 615 break; 616 } 617 } 618 619 Vector3 DoTangent(Vector3 p0, Vector3 t0, int cid, int pointIndex, Color color) 620 { 621 var handleSize = GetHandleSizeForPoint(pointIndex); 622 var tangentSize = GetTangentSizeForPoint(pointIndex); 623 624 handles.color = color; 625 626 float distance = HandleUtility.DistanceToCircle(t0, tangentSize); 627 628 if (lineTexture != null) 629 handles.DrawAAPolyLine(lineTexture, new Vector3[] {p0, t0}); 630 else 631 handles.DrawLine(p0, t0); 632 633 handleOutlineColor = distance > 0f ? color : k_OutlineColor[(int)ColorEnum.ESelectedHovered]; 634 handleFillColor = color; 635 var delta = DoSlider(cid, t0, Vector3.up, Vector3.right, tangentSize, GetCapForTangent(pointIndex)) - p0; 636 637 return delta.magnitude < handleSize ? Vector3.zero : delta; 638 } 639 640 public void HandlePointClick(int pointIndex) 641 { 642 if (m_Selection.Count > 1) 643 { 644 m_Selection.SelectPoint(pointIndex, SelectionType.Normal); 645 } 646 else if (!currentEvent.control && !currentEvent.shift && m_ActivePointOnLastMouseDown == activePoint) 647 { 648 OnPointClick(pointIndex); 649 } 650 } 651 652 public void CycleTangentMode() 653 { 654 TangentMode oldMode = GetTangentMode(activePoint); 655 TangentMode newMode = GetNextTangentMode(oldMode); 656 SetTangentMode(activePoint, newMode); 657 RefreshTangentsAfterModeChange(activePoint, oldMode, newMode); 658 } 659 660 public static TangentMode GetNextTangentMode(TangentMode current) 661 { 662 return (TangentMode)((((int)current) + 1) % Enum.GetValues(typeof(TangentMode)).Length); 663 } 664 665 public void RefreshTangentsAfterModeChange(int pointIndex, TangentMode oldMode, TangentMode newMode) 666 { 667 if (oldMode != TangentMode.Linear && newMode == TangentMode.Linear) 668 { 669 SetPointLTangent(pointIndex, Vector3.zero); 670 SetPointRTangent(pointIndex, Vector3.zero); 671 } 672 if (newMode == TangentMode.Continuous) 673 { 674 if (oldMode == TangentMode.Broken) 675 { 676 SetPointRTangent(pointIndex, GetPointLTangent(pointIndex) * -1f); 677 } 678 if (oldMode == TangentMode.Linear) 679 { 680 FromAllZeroToTangents(pointIndex); 681 } 682 } 683 } 684 685 private ColorEnum GetColorForPoint(int pointIndex, int handleID) 686 { 687 bool hovered = MouseDistanceToPoint(pointIndex) <= 0f; 688 bool selected = m_Selection.Contains(pointIndex); 689 690 if (hovered && selected || GUIUtility.hotControl == handleID) 691 return ColorEnum.ESelectedHovered; 692 if (hovered) 693 return ColorEnum.EUnselectedHovered; 694 if (selected) 695 return ColorEnum.ESelected; 696 697 return ColorEnum.EUnselected; 698 } 699 700 private void FromAllZeroToTangents(int pointIndex) 701 { 702 Vector3 p0 = GetPointPosition(pointIndex); 703 704 int prevPointIndex = pointIndex > 0 ? pointIndex - 1 : GetPointsCount() - 1; 705 706 Vector3 lt = (GetPointPosition(prevPointIndex) - p0) * .33f; 707 Vector3 rt = -lt; 708 709 const float maxScreenLen = 100f; 710 711 float ltScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + lt)).magnitude; 712 float rtScreenLen = (LocalToScreen(p0) - LocalToScreen(p0 + rt)).magnitude; 713 714 lt *= Mathf.Min(maxScreenLen / ltScreenLen, 1f); 715 rt *= Mathf.Min(maxScreenLen / rtScreenLen, 1f); 716 717 this.SetPointLTangent(pointIndex, lt); 718 this.SetPointRTangent(pointIndex, rt); 719 } 720 721 private Handles.CapFunction GetCapForTangent(int index) 722 { 723 if (GetTangentMode(index) == TangentMode.Continuous) 724 return CircleCap; 725 726 return DiamondCap; 727 } 728 729 private DistanceToControl GetDistanceFuncForTangent(int index) 730 { 731 if (GetTangentMode(index) == TangentMode.Continuous) 732 return DistanceToCircle(); 733 734 return HandleUtility.DistanceToDiamond; 735 } 736 737 private Handles.CapFunction GetCapForPoint(int index) 738 { 739 switch (GetTangentMode(index)) 740 { 741 case TangentMode.Broken: 742 return DiamondCap; 743 case TangentMode.Continuous: 744 return CircleCap; 745 case TangentMode.Linear: 746 return RectCap; 747 } 748 return DiamondCap; 749 } 750 751 private static float DistanceToCircleInternal(Vector3 position, Quaternion rotation, float size) 752 { 753 return HandleUtility.DistanceToCircle(position, size); 754 } 755 756 private float GetHandleSizeForPoint(int index) 757 { 758 return Camera.current != null ? HandleUtility.GetHandleSize(GetPointPosition(index)) * 0.075f : GetHandleSize(); 759 } 760 761 private float GetTangentSizeForPoint(int index) 762 { 763 return GetHandleSizeForPoint(index) * 0.8f; 764 } 765 766 private void RefreshTangents(int index, bool rightIsActive) 767 { 768 var m = GetTangentMode(index); 769 var lt = GetPointLTangent(index); 770 var rt = GetPointRTangent(index); 771 772 // mirror the change on the other tangent for continuous 773 if (m == TangentMode.Continuous) 774 { 775 if (rightIsActive) 776 { 777 lt = -rt; 778 var mag = lt.magnitude; 779 lt = lt.normalized * mag; 780 } 781 else 782 { 783 rt = -lt; 784 var mag = rt.magnitude; 785 rt = rt.normalized * mag; 786 } 787 } 788 789 this.SetPointLTangent(activePoint, lt); 790 this.SetPointRTangent(activePoint, rt); 791 } 792 793 private void StoreMouseDownState() 794 { 795 m_MousePositionLastMouseDown = currentEvent.mousePosition; 796 m_ActivePointOnLastMouseDown = activePoint; 797 } 798 799 private void DelayedResetIfNecessary() 800 { 801 if (m_DelayedReset) 802 { 803 guiUtility.hotControl = 0; 804 guiUtility.keyboardControl = 0; 805 m_Selection.Clear(); 806 activePoint = -1; 807 m_DelayedReset = false; 808 } 809 } 810 811 public Vector3 FindClosestPointOnEdge(int edgeIndex, Vector3 position, int iterations) 812 { 813 float step = 1f / iterations; 814 float closestDistance = float.MaxValue; 815 float closestDistanceIndex = edgeIndex; 816 817 for (float a = 0f; a <= 1f; a += step) 818 { 819 Vector3 pos = GetPositionByIndex(edgeIndex + a); 820 float distance = (position - pos).sqrMagnitude; 821 if (distance < closestDistance) 822 { 823 closestDistance = distance; 824 closestDistanceIndex = edgeIndex + a; 825 } 826 } 827 828 return GetPositionByIndex(closestDistanceIndex); 829 } 830 831 private Vector3 GetPositionByIndex(float index) 832 { 833 int indexA = Mathf.FloorToInt(index); 834 int indexB = NextIndex(indexA, GetPointsCount()); 835 float subPosition = index - indexA; 836 return GetPoint(GetPointPosition(indexA), GetPointPosition(indexB), GetPointRTangent(indexA), GetPointLTangent(indexB), subPosition); 837 } 838 839 private static Vector3 GetPoint(Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float t) 840 { 841 t = Mathf.Clamp01(t); 842 float a = 1f - t; 843 return a * a * a * startPosition + 3f * a * a * t * (startPosition + startTangent) + 3f * a * t * t * (endPosition + endTangent) + t * t * t * endPosition; 844 } 845 846 private int FindClosestPointToMouse() 847 { 848 Vector3 mousePos = ScreenToLocal(currentEvent.mousePosition); 849 return FindClosestPointIndex(mousePos); 850 } 851 852 private float MouseDistanceToClosestTangent() 853 { 854 if (activePoint < 0) 855 return float.MaxValue; 856 857 var lt = GetPointLTangent(activePoint); 858 var rt = GetPointRTangent(activePoint); 859 860 if (lt.sqrMagnitude == 0f && rt.sqrMagnitude == 0f) 861 return float.MaxValue; 862 863 var p = GetPointPosition(activePoint); 864 var tangentSize = GetTangentSizeForPoint(activePoint); 865 866 return Mathf.Min( 867 HandleUtility.DistanceToRectangle(p + lt, Quaternion.identity, tangentSize), 868 HandleUtility.DistanceToRectangle(p + rt, Quaternion.identity, tangentSize) 869 ); 870 } 871 872 private int FindClosestPointIndex(Vector3 position) 873 { 874 float closestDistance = float.MaxValue; 875 int closestIndex = -1; 876 for (int i = 0; i < this.GetPointsCount(); i++) 877 { 878 var p0 = this.GetPointPosition(i); 879 var distance = (p0 - position).sqrMagnitude; 880 if (distance < closestDistance) 881 { 882 closestIndex = i; 883 closestDistance = distance; 884 } 885 } 886 return closestIndex; 887 } 888 889 private DistanceToControl GetDistanceFuncForPoint(int index) 890 { 891 switch (GetTangentMode(index)) 892 { 893 case TangentMode.Broken: 894 return DistanceToDiamond(); 895 case TangentMode.Continuous: 896 return DistanceToCircle(); 897 case TangentMode.Linear: 898 return DistanceToRectangle(); 899 } 900 return DistanceToRectangle(); 901 } 902 903 private float MouseDistanceToPoint(int index) 904 { 905 switch (GetTangentMode(index)) 906 { 907 case TangentMode.Broken: 908 return HandleUtility.DistanceToDiamond(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index)); 909 case TangentMode.Linear: 910 return HandleUtility.DistanceToRectangle(GetPointPosition(index), Quaternion.identity, GetHandleSizeForPoint(index)); 911 case TangentMode.Continuous: 912 return HandleUtility.DistanceToCircle(GetPointPosition(index), GetHandleSizeForPoint(index)); 913 } 914 return float.MaxValue; 915 } 916 917 private bool EdgeDragModifiersActive() 918 { 919 return currentEvent.modifiers == EventModifiers.Control; 920 } 921 922 private static Vector3 DoSlider(int id, Vector3 position, Vector3 slide1, Vector3 slide2, float s, Handles.CapFunction cap) 923 { 924 return Slider2D.Do(id, position, Vector3.zero, Vector3.Cross(slide1, slide2), slide1, slide2, s, cap, Vector2.zero, false); 925 } 926 927 public void RectCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType) 928 { 929 if (eventType == EventType.Layout || eventType == EventType.MouseMove) 930 { 931 HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f)); 932 } 933 else if (eventType == EventType.Repaint) 934 { 935 Vector3 normal = handles.matrix.GetColumn(2); 936 Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized; 937 Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp); 938 Vector3 sideways = localRotation * Vector3.right * size; 939 Vector3 up = localRotation * Vector3.up * size; 940 List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES)); 941 list.Add(position + sideways + up); 942 list.Add(position + sideways - up); 943 list.Add(position - sideways - up); 944 list.Add(position - sideways - up); 945 list.Add(position - sideways + up); 946 list.Add(position + sideways + up); 947 948 list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES)); 949 list.Add(position + sideways + up); 950 list.Add(position + sideways - up); 951 list.Add(position + sideways - up); 952 list.Add(position - sideways - up); 953 list.Add(position - sideways - up); 954 list.Add(position - sideways + up); 955 list.Add(position - sideways + up); 956 list.Add(position + sideways + up); 957 } 958 } 959 960 public void CircleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType) 961 { 962 if (eventType == EventType.Layout || eventType == EventType.MouseMove) 963 { 964 HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f)); 965 } 966 else if (eventType == EventType.Repaint) 967 { 968 Vector3 forward = handleMatrixrotation * rotation * Vector3.forward; 969 Vector3 tangent = Vector3.Cross(forward, Vector3.up); 970 if (tangent.sqrMagnitude < .001f) 971 tangent = Vector3.Cross(forward, Vector3.right); 972 973 Vector3[] points = new Vector3[60]; 974 handles.SetDiscSectionPoints(points, position, forward, tangent, 360f, size); 975 976 List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES)); 977 for (int i = 1; i < points.Length; i++) 978 { 979 list.Add(position); 980 list.Add(points[i]); 981 list.Add(points[i - 1]); 982 } 983 984 list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES)); 985 for (int i = 0; i < points.Length - 1; i++) 986 { 987 list.Add(points[i]); 988 list.Add(points[i + 1]); 989 } 990 } 991 } 992 993 public void DiamondCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType) 994 { 995 if (eventType == EventType.Layout || eventType == EventType.MouseMove) 996 { 997 HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(position, size * 0.5f)); 998 } 999 else if (eventType == EventType.Repaint) 1000 { 1001 Vector3 normal = handles.matrix.GetColumn(2); 1002 Vector3 projectedUp = (ProjectPointOnPlane(normal, position, position + Vector3.up) - position).normalized; 1003 Quaternion localRotation = Quaternion.LookRotation(handles.matrix.GetColumn(2), projectedUp); 1004 Vector3 sideways = localRotation * Vector3.right * size * 1.25f; 1005 Vector3 up = localRotation * Vector3.up * size * 1.25f; 1006 1007 List<Vector3> list = GetDrawBatchList(new DrawBatchDataKey(handleFillColor, GL.TRIANGLES)); 1008 list.Add(position - up); 1009 list.Add(position + sideways); 1010 list.Add(position - sideways); 1011 list.Add(position - sideways); 1012 list.Add(position + up); 1013 list.Add(position + sideways); 1014 1015 list = GetDrawBatchList(new DrawBatchDataKey(handleOutlineColor, GL.LINES)); 1016 list.Add(position + sideways); 1017 list.Add(position - up); 1018 list.Add(position - up); 1019 list.Add(position - sideways); 1020 list.Add(position - sideways); 1021 list.Add(position + up); 1022 list.Add(position + up); 1023 list.Add(position + sideways); 1024 } 1025 } 1026 1027 private static int NextIndex(int index, int total) 1028 { 1029 return mod(index + 1, total); 1030 } 1031 1032 private static int PreviousIndex(int index, int total) 1033 { 1034 return mod(index - 1, total); 1035 } 1036 1037 private static int mod(int x, int m) 1038 { 1039 int r = x % m; 1040 return r < 0 ? r + m : r; 1041 } 1042 1043 private static Vector3 ProjectPointOnPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 point) 1044 { 1045 planeNormal.Normalize(); 1046 var distance = -Vector3.Dot(planeNormal.normalized, (point - planePoint)); 1047 return point + planeNormal * distance; 1048 } 1049 1050 public void RegisterToShapeEditor(ShapeEditor se) 1051 { 1052 ++m_ShapeEditorRegisteredTo; 1053 se.m_ShapeEditorListeners.Add(this); 1054 } 1055 1056 public void UnregisterFromShapeEditor(ShapeEditor se) 1057 { 1058 --m_ShapeEditorRegisteredTo; 1059 se.m_ShapeEditorListeners.Remove(this); 1060 } 1061 1062 private void OnShapeEditorUpdateDone() 1063 { 1064 // When all the ShapeEditor we are interested in 1065 // has completed their update, we reset our internal values 1066 ++m_ShapeEditorUpdateDone; 1067 if (m_ShapeEditorUpdateDone >= m_ShapeEditorRegisteredTo) 1068 { 1069 m_ShapeEditorUpdateDone = 0; 1070 m_MouseClosestEdge = -1; 1071 m_MouseClosestEdgeDist = float.MaxValue; 1072 m_EdgePoints = null; 1073 } 1074 } 1075 1076 private void ClearSelectedPoints() 1077 { 1078 selectedPoints.Clear(); 1079 activePoint = -1; 1080 } 1081 1082 private void SelectPointsInRect(Rect r, SelectionType st) 1083 { 1084 var localRect = EditorGUIExt.FromToRect(ScreenToLocal(r.min), ScreenToLocal(r.max)); 1085 m_Selection.RectSelect(localRect, st); 1086 } 1087 1088 private void DeleteSelections() 1089 { 1090 foreach (var se in m_ShapeEditorListeners) 1091 se.m_Selection.DeleteSelection(); 1092 m_Selection.DeleteSelection(); 1093 } 1094 1095 private void MoveSelections(Vector2 distance) 1096 { 1097 foreach (var se in m_ShapeEditorListeners) 1098 se.m_Selection.MoveSelection(distance); 1099 m_Selection.MoveSelection(distance); 1100 } 1101 1102 private void SelectPoint(int index, SelectionType st) 1103 { 1104 if (st == SelectionType.Normal) 1105 { 1106 foreach (var se in m_ShapeEditorListeners) 1107 se.ClearSelectedPoints(); 1108 } 1109 m_Selection.SelectPoint(index, st); 1110 } 1111 } 1112}