A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using UnityEngine;
5using UnityEditor;
6using UnityEditor.U2D.Common.Path.GUIFramework;
7
8namespace UnityEditor.U2D.Common.Path
9{
10 internal class PathEditor
11 {
12 const float kSnappingDistance = 15f;
13 const string kDeleteCommandName = "Delete";
14 const string kSoftDeleteCommandName = "SoftDelete";
15 public IEditablePathController controller { get; set; }
16 public bool linearTangentIsZero { get; set; }
17 private IDrawer m_Drawer = new Drawer();
18 private IDrawer m_DrawerOverride;
19 private GUISystem m_GUISystem;
20
21 public IDrawer drawerOverride { get; set; }
22
23 private IDrawer drawer
24 {
25 get
26 {
27 if (drawerOverride != null)
28 return drawerOverride;
29
30 return m_Drawer;
31 }
32 }
33
34 public PathEditor() : this(new GUISystem(new GUIState())) { }
35
36 public PathEditor(GUISystem guiSystem)
37 {
38 m_GUISystem = guiSystem;
39
40 var m_PointControl = new GenericControl("Point")
41 {
42 count = GetPointCount,
43 distance = (guiState, i) =>
44 {
45 var position = GetPoint(i).position;
46 return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
47 },
48 position = (i) => { return GetPoint(i).position; },
49 forward = (i) => { return GetForward(); },
50 up = (i) => { return GetUp(); },
51 right = (i) => { return GetRight(); },
52 onRepaint = DrawPoint
53 };
54
55 var m_EdgeControl = new GenericControl("Edge")
56 {
57 count = GetEdgeCount,
58 distance = DistanceToEdge,
59 position = (i) => { return GetPoint(i).position; },
60 forward = (i) => { return GetForward(); },
61 up = (i) => { return GetUp(); },
62 right = (i) => { return GetRight(); },
63 onRepaint = DrawEdge
64 };
65 m_EdgeControl.onEndLayout = (guiState) => { controller.AddClosestPath(m_EdgeControl.layoutData.distance); };
66
67 var m_LeftTangentControl = new GenericControl("LeftTangent")
68 {
69 count = () =>
70 {
71 if (GetShapeType() != ShapeType.Spline)
72 return 0;
73
74 return GetPointCount();
75 },
76 distance = (guiState, i) =>
77 {
78 if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
79 return float.MaxValue;
80
81 if (!IsSelected(i) || IsOpenEnded() && i == 0)
82 return float.MaxValue;
83
84 var position = GetLeftTangent(i);
85 return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
86 },
87 position = (i) => { return GetLeftTangent(i); },
88 forward = (i) => { return GetForward(); },
89 up = (i) => { return GetUp(); },
90 right = (i) => { return GetRight(); },
91 onRepaint = (guiState, control, i) =>
92 {
93 if (!IsSelected(i) || IsOpenEnded() && i == 0)
94 return;
95
96 var point = GetPoint(i);
97
98 if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
99 return;
100
101 var position = point.position;
102 var leftTangent = GetLeftTangent(i);
103
104 drawer.DrawTangent(position, leftTangent, HandleSettings.tangentColor);
105 }
106 };
107
108 var m_RightTangentControl = new GenericControl("RightTangent")
109 {
110 count = () =>
111 {
112 if (GetShapeType() != ShapeType.Spline)
113 return 0;
114
115 return GetPointCount();
116 },
117 distance = (guiState, i) =>
118 {
119 if (linearTangentIsZero && GetPoint(i).tangentMode == TangentMode.Linear)
120 return float.MaxValue;
121
122 if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
123 return float.MaxValue;
124
125 var position = GetRightTangent(i);
126 return guiState.DistanceToCircle(position, guiState.GetHandleSize(position) * 10f);
127 },
128 position = (i) => { return GetRightTangent(i); },
129 forward = (i) => { return GetForward(); },
130 up = (i) => { return GetUp(); },
131 right = (i) => { return GetRight(); },
132 onRepaint = (guiState, control, i) =>
133 {
134 if (!IsSelected(i) || IsOpenEnded() && i == GetPointCount()-1)
135 return;
136
137 var point = GetPoint(i);
138
139 if (linearTangentIsZero && point.tangentMode == TangentMode.Linear)
140 return;
141
142 var position = point.position;
143 var rightTangent = GetRightTangent(i);
144
145 drawer.DrawTangent(position, rightTangent, HandleSettings.tangentColor);
146 }
147 };
148
149 var m_CreatePointAction = new CreatePointAction(m_PointControl, m_EdgeControl)
150 {
151 enable = (guiState, action) => !IsAltDown(guiState) && !guiState.isActionKeyDown && controller.closestEditablePath == controller.editablePath,
152 enableRepaint = (guiState, action) => EnableCreatePointRepaint(guiState, m_PointControl, m_LeftTangentControl, m_RightTangentControl),
153 repaintOnMouseMove = (guiState, action) => true,
154 guiToWorld = GUIToWorld,
155 onCreatePoint = (index, position) =>
156 {
157 controller.RegisterUndo("Create Point");
158 controller.CreatePoint(index, position);
159 },
160 onPreRepaint = (guiState, action) =>
161 {
162 if (GetPointCount() > 0)
163 {
164 var position = ClosestPointInEdge(guiState, guiState.mousePosition, m_EdgeControl.layoutData.index);
165 drawer.DrawCreatePointPreview(position, ControlPointSettings.controlPointColor);
166 }
167 }
168 };
169
170 Action<IGUIState> removePoints = (guiState) =>
171 {
172 controller.RegisterUndo("Remove Point");
173 controller.RemoveSelectedPoints();
174 guiState.changed = true;
175 };
176
177 var m_RemovePointAction1 = new CommandAction(kDeleteCommandName)
178 {
179 enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
180 onCommand = removePoints
181 };
182
183 var m_RemovePointAction2 = new CommandAction(kSoftDeleteCommandName)
184 {
185 enable = (guiState, action) => { return GetSelectedPointCount() > 0; },
186 onCommand = removePoints
187 };
188
189 var dragged = false;
190 var m_MovePointAction = new SliderAction(m_PointControl)
191 {
192 enable = (guiState, action) => !IsAltDown(guiState),
193 onClick = (guiState, control) =>
194 {
195 dragged = false;
196 var index = control.layoutData.index;
197
198 if (!IsSelected(index))
199 {
200 controller.RegisterUndo("Selection");
201
202 if (!guiState.isActionKeyDown)
203 controller.ClearSelection();
204
205 controller.SelectPoint(index, true);
206 guiState.changed = true;
207 }
208 },
209 onSliderChanged = (guiState, control, position) =>
210 {
211 var index = control.hotLayoutData.index;
212 var delta = SnapIfNeeded(position) - GetPoint(index).position;
213
214 if (!dragged)
215 {
216 controller.RegisterUndo("Move Point");
217 dragged = true;
218 }
219
220 controller.MoveSelectedPoints(delta);
221 }
222 };
223
224 var m_MoveEdgeAction = new SliderAction(m_EdgeControl)
225 {
226 enable = (guiState, action) => !IsAltDown(guiState) && guiState.isActionKeyDown,
227 onSliderBegin = (guiState, control, position) =>
228 {
229 dragged = false;
230
231 },
232 onSliderChanged = (guiState, control, position) =>
233 {
234 var index = control.hotLayoutData.index;
235 var delta = position - GetPoint(index).position;
236
237 if (!dragged)
238 {
239 controller.RegisterUndo("Move Edge");
240 dragged = true;
241 }
242
243 controller.MoveEdge(index, delta);
244 }
245 };
246
247 var cachedRightTangent = Vector3.zero;
248 var cachedLeftTangent = Vector3.zero;
249 var cachedTangentMode = TangentMode.Linear;
250
251 var m_MoveLeftTangentAction = new SliderAction(m_LeftTangentControl)
252 {
253 enable = (guiState, action) => !IsAltDown(guiState),
254 onSliderBegin = (guiState, control, position) =>
255 {
256 dragged = false;
257 var point = GetPoint(control.hotLayoutData.index);
258 cachedRightTangent = point.rightTangent;
259 cachedTangentMode = point.tangentMode;
260 },
261 onSliderChanged = (guiState, control, position) =>
262 {
263 var index = control.hotLayoutData.index;
264 var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
265
266 if (!dragged)
267 {
268 controller.RegisterUndo("Move Tangent");
269 dragged = true;
270 }
271
272 position = SnapIfNeeded(position);
273 controller.SetLeftTangent(index, position, setToLinear, guiState.isShiftDown, cachedRightTangent, cachedTangentMode);
274
275 }
276 };
277
278 var m_MoveRightTangentAction = new SliderAction(m_RightTangentControl)
279 {
280 enable = (guiState, action) => !IsAltDown(guiState),
281 onSliderBegin = (guiState, control, position) =>
282 {
283 dragged = false;
284 var point = GetPoint(control.hotLayoutData.index);
285 cachedLeftTangent = point.leftTangent;
286 cachedTangentMode = point.tangentMode;
287 },
288 onSliderChanged = (guiState, control, position) =>
289 {
290 var index = control.hotLayoutData.index;
291 var setToLinear = m_PointControl.distance(guiState, index) <= DefaultControl.kPickDistance;
292
293 if (!dragged)
294 {
295 controller.RegisterUndo("Move Tangent");
296 dragged = true;
297 }
298
299 position = SnapIfNeeded(position);
300 controller.SetRightTangent(index, position, setToLinear, guiState.isShiftDown, cachedLeftTangent, cachedTangentMode);
301 }
302 };
303
304 m_GUISystem.AddControl(m_EdgeControl);
305 m_GUISystem.AddControl(m_PointControl);
306 m_GUISystem.AddControl(m_LeftTangentControl);
307 m_GUISystem.AddControl(m_RightTangentControl);
308 m_GUISystem.AddAction(m_CreatePointAction);
309 m_GUISystem.AddAction(m_RemovePointAction1);
310 m_GUISystem.AddAction(m_RemovePointAction2);
311 m_GUISystem.AddAction(m_MovePointAction);
312 m_GUISystem.AddAction(m_MoveEdgeAction);
313 m_GUISystem.AddAction(m_MoveLeftTangentAction);
314 m_GUISystem.AddAction(m_MoveRightTangentAction);
315 }
316
317 public void OnGUI()
318 {
319 m_GUISystem.OnGUI();
320 }
321
322 private bool IsAltDown(IGUIState guiState)
323 {
324 return guiState.hotControl == 0 && guiState.isAltDown;
325 }
326
327 private ControlPoint GetPoint(int index)
328 {
329 return controller.editablePath.GetPoint(index);
330 }
331
332 private int GetPointCount()
333 {
334 return controller.editablePath.pointCount;
335 }
336
337 private int GetEdgeCount()
338 {
339 if (controller.editablePath.isOpenEnded)
340 return controller.editablePath.pointCount - 1;
341
342 return controller.editablePath.pointCount;
343 }
344
345 private int GetSelectedPointCount()
346 {
347 return controller.editablePath.selection.Count;
348 }
349
350 private bool IsSelected(int index)
351 {
352 return controller.editablePath.selection.Contains(index);
353 }
354
355 private Vector3 GetForward()
356 {
357 return controller.editablePath.forward;
358 }
359
360 private Vector3 GetUp()
361 {
362 return controller.editablePath.up;
363 }
364
365 private Vector3 GetRight()
366 {
367 return controller.editablePath.right;
368 }
369
370 private Matrix4x4 GetLocalToWorldMatrix()
371 {
372 return controller.editablePath.localToWorldMatrix;
373 }
374
375 private ShapeType GetShapeType()
376 {
377 return controller.editablePath.shapeType;
378 }
379
380 private bool IsOpenEnded()
381 {
382 return controller.editablePath.isOpenEnded;
383 }
384
385 private Vector3 GetLeftTangent(int index)
386 {
387 if (linearTangentIsZero)
388 return GetPoint(index).leftTangent;
389
390 return controller.editablePath.CalculateLeftTangent(index);
391 }
392
393 private Vector3 GetRightTangent(int index)
394 {
395 if (linearTangentIsZero)
396 return GetPoint(index).rightTangent;
397
398 return controller.editablePath.CalculateRightTangent(index);
399 }
400
401 private int NextIndex(int index)
402 {
403 return EditablePathUtility.Mod(index + 1, GetPointCount());
404 }
405
406 private ControlPoint NextControlPoint(int index)
407 {
408 return GetPoint(NextIndex(index));
409 }
410
411 private int PrevIndex(int index)
412 {
413 return EditablePathUtility.Mod(index - 1, GetPointCount());
414 }
415
416 private ControlPoint PrevControlPoint(int index)
417 {
418 return GetPoint(PrevIndex(index));
419 }
420
421 private Vector3 ClosestPointInEdge(IGUIState guiState, Vector2 mousePosition, int index)
422 {
423 if (GetShapeType() == ShapeType.Polygon)
424 {
425 var p0 = GetPoint(index).position;
426 var p1 = NextControlPoint(index).position;
427 var mouseWorldPosition = GUIToWorld(guiState, mousePosition);
428
429 var dir1 = (mouseWorldPosition - p0);
430 var dir2 = (p1 - p0);
431
432 return Mathf.Clamp01(Vector3.Dot(dir1, dir2.normalized) / dir2.magnitude) * dir2 + p0;
433 }
434 else if (GetShapeType() == ShapeType.Spline)
435 {
436 var nextIndex = NextIndex(index);
437 float t;
438 return BezierUtility.ClosestPointOnCurve(
439 GUIToWorld(guiState, mousePosition),
440 GetPoint(index).position,
441 GetPoint(nextIndex).position,
442 GetRightTangent(index),
443 GetLeftTangent(nextIndex),
444 out t);
445 }
446
447 return Vector3.zero;
448 }
449
450 private float DistanceToEdge(IGUIState guiState, int index)
451 {
452 if (GetShapeType() == ShapeType.Polygon)
453 {
454 return guiState.DistanceToSegment(GetPoint(index).position, NextControlPoint(index).position);
455 }
456 else if (GetShapeType() == ShapeType.Spline)
457 {
458 var closestPoint = ClosestPointInEdge(guiState, guiState.mousePosition, index);
459 var closestPoint2 = HandleUtility.WorldToGUIPoint(closestPoint);
460
461 return (closestPoint2 - guiState.mousePosition).magnitude;
462 }
463
464 return float.MaxValue;
465 }
466
467 private Vector3 GUIToWorld(IGUIState guiState, Vector2 position)
468 {
469 return guiState.GUIToWorld(position, GetForward(), GetLocalToWorldMatrix().MultiplyPoint3x4(Vector3.zero));
470 }
471
472 private void DrawPoint(IGUIState guiState, Control control, int index)
473 {
474 var position = GetPoint(index).position;
475 var isTangent = (control.name == "LeftTangent" || control.name == "RightTangent");
476 if (guiState.hotControl == control.actionID && control.hotLayoutData.index == index || IsSelected(index))
477 drawer.DrawPointSelected(position, ControlPointSettings.controlPointSelectedColor);
478 else if (guiState.hotControl == 0 && guiState.nearestControl == control.ID && !IsAltDown(guiState) && control.layoutData.index == index)
479 drawer.DrawPointHovered(position, ControlPointSettings.controlPointSelectedColor);
480 else
481 drawer.DrawPoint(position, isTangent ? HandleSettings.tangentColor : ControlPointSettings.controlPointColor);
482 }
483
484 private void DrawEdge(IGUIState guiState, Control control, int index)
485 {
486 if (GetShapeType() == ShapeType.Polygon)
487 {
488 var nextIndex = NextIndex(index);
489 var color = HandleSettings.splineColor;
490
491 if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
492 color = HandleSettings.splineHoveredColor;
493
494 drawer.DrawLine(GetPoint(index).position, GetPoint(nextIndex).position, 5f, color);
495 }
496 else if (GetShapeType() == ShapeType.Spline)
497 {
498 var nextIndex = NextIndex(index);
499 var color = HandleSettings.splineColor;
500
501 if(guiState.nearestControl == control.ID && control.layoutData.index == index && guiState.hotControl == 0 && !IsAltDown(guiState))
502 color = HandleSettings.splineHoveredColor;
503
504 drawer.DrawBezier(
505 GetPoint(index).position,
506 GetRightTangent(index),
507 GetLeftTangent(nextIndex),
508 GetPoint(nextIndex).position,
509 5f,
510 color);
511 }
512 }
513
514 private bool EnableCreatePointRepaint(IGUIState guiState, Control pointControl, Control leftTangentControl, Control rightTangentControl)
515 {
516 return guiState.nearestControl != pointControl.ID &&
517 guiState.hotControl == 0 &&
518 (guiState.nearestControl != leftTangentControl.ID) &&
519 (guiState.nearestControl != rightTangentControl.ID);
520 }
521
522 private Vector3 SnapIfNeeded(Vector3 position)
523 {
524 if (!controller.enableSnapping || controller.snapping == null)
525 return position;
526
527 var guiPosition = HandleUtility.WorldToGUIPoint(position);
528 var snappedGuiPosition = HandleUtility.WorldToGUIPoint(controller.snapping.Snap(position));
529 var sqrDistance = (guiPosition - snappedGuiPosition).sqrMagnitude;
530
531 if (sqrDistance < kSnappingDistance * kSnappingDistance)
532 position = controller.snapping.Snap(position);
533
534 return position;
535 }
536 }
537}