A game about forced loneliness, made by TACStudios
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}