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}