A game about forced loneliness, made by TACStudios
at master 644 lines 22 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Text.RegularExpressions; 4using UnityEngine; 5 6namespace UnityEditor.U2D.Animation 7{ 8 [Serializable] 9 internal class SkeletonController 10 { 11 static readonly string k_DefaultRootName = "root"; 12 static readonly string k_DefaultBoneName = "bone"; 13 static Regex s_Regex = new Regex(@"\w+_\d+$", RegexOptions.IgnoreCase); 14 15 [SerializeField] 16 Vector3 m_CreateBoneStartPosition; 17 [SerializeField] 18 BoneCache m_PrevCreatedBone; 19 20 SkeletonCache m_Skeleton; 21 bool m_Moved = false; 22 23 ISkeletonStyle style 24 { 25 get 26 { 27 if (styleOverride != null) 28 return styleOverride; 29 30 return SkeletonStyles.Default; 31 } 32 } 33 34 SkinningCache skinningCache => m_Skeleton.skinningCache; 35 36 BoneCache selectedBone 37 { 38 get => selection.activeElement.ToSpriteSheetIfNeeded(); 39 set => selection.activeElement = value.ToCharacterIfNeeded(); 40 } 41 42 BoneCache[] selectedBones 43 { 44 get => selection.elements.ToSpriteSheetIfNeeded(); 45 set => selection.elements = value.ToCharacterIfNeeded(); 46 } 47 48 BoneCache rootBone => selection.root.ToSpriteSheetIfNeeded(); 49 BoneCache[] rootBones => selection.roots.ToSpriteSheetIfNeeded(); 50 51 public ISkeletonView view { get; set; } 52 public ISkeletonStyle styleOverride { get; set; } 53 public IBoneSelection selection { get; set; } 54 public bool editBindPose { get; set; } 55 56 public SkeletonCache skeleton 57 { 58 get => m_Skeleton; 59 set => SetSkeleton(value); 60 } 61 62 public BoneCache hoveredBone => GetBone(view.hoveredBoneID); 63 public BoneCache hoveredTail => GetBone(view.hoveredTailID); 64 public BoneCache hoveredBody => GetBone(view.hoveredBodyID); 65 public BoneCache hoveredJoint => GetBone(view.hoveredJointID); 66 public BoneCache hotBone => GetBone(view.hotBoneID); 67 68 BoneCache GetBone(int instanceID) 69 { 70 return BaseObject.InstanceIDToObject(instanceID) as BoneCache; 71 } 72 73 void SetSkeleton(SkeletonCache newSkeleton) 74 { 75 if (skeleton != newSkeleton) 76 { 77 m_Skeleton = newSkeleton; 78 Reset(); 79 } 80 } 81 82 public void Reset() 83 { 84 view.DoCancelMultistepAction(true); 85 } 86 87 public void OnGUI() 88 { 89 if (skeleton == null) 90 return; 91 92 view.BeginLayout(); 93 94 if (view.CanLayout()) 95 LayoutBones(); 96 97 view.EndLayout(); 98 99 HandleSelectBone(); 100 HandleRotateBone(); 101 HandleMoveBone(); 102 HandleFreeMoveBone(); 103 HandleMoveJoint(); 104 HandleMoveEndPosition(); 105 HandleChangeLength(); 106 HandleCreateBone(); 107 HandleSplitBone(); 108 HandleRemoveBone(); 109 HandleCancelMultiStepAction(); 110 DrawSkeleton(); 111 DrawSplitBonePreview(); 112 DrawCreateBonePreview(); 113 DrawCursors(); 114 115 BatchedDrawing.Draw(); 116 } 117 118 void LayoutBones() 119 { 120 for (var i = 0; i < skeleton.boneCount; ++i) 121 { 122 var bone = skeleton.GetBone(i); 123 124 if (bone.isVisible && bone != hotBone) 125 view.LayoutBone(bone.GetInstanceID(), bone.position, bone.endPosition, bone.forward, bone.up, bone.right, bone.chainedChild == null); 126 } 127 } 128 129 void HandleSelectBone() 130 { 131 if (view.DoSelectBone(out var instanceID, out var additive)) 132 { 133 var bone = GetBone(instanceID).ToCharacterIfNeeded(); 134 135 using (skinningCache.UndoScope(TextContent.boneSelection, true)) 136 { 137 if (!additive) 138 { 139 if (!selection.Contains(bone)) 140 selectedBone = bone; 141 } 142 else 143 selection.Select(bone, !selection.Contains(bone)); 144 145 skinningCache.events.boneSelectionChanged.Invoke(); 146 } 147 } 148 } 149 150 void HandleRotateBone() 151 { 152 if (view.IsActionTriggering(SkeletonAction.RotateBone)) 153 m_Moved = false; 154 155 var pivot = hoveredBone; 156 157 if (view.IsActionHot(SkeletonAction.RotateBone)) 158 pivot = hotBone; 159 160 if (pivot == null) 161 return; 162 163 var selectedRootBones = selection.roots.ToSpriteSheetIfNeeded(); 164 pivot = pivot.FindRoot<BoneCache>(selectedRootBones); 165 166 if (pivot == null) 167 return; 168 169 if (view.DoRotateBone(pivot.position, pivot.forward, out var deltaAngle)) 170 { 171 if (!m_Moved) 172 { 173 skinningCache.BeginUndoOperation(TextContent.rotateBone); 174 m_Moved = true; 175 } 176 177 m_Skeleton.RotateBones(selectedBones, deltaAngle); 178 InvokePoseChanged(); 179 } 180 } 181 182 void HandleMoveBone() 183 { 184 if (view.IsActionTriggering(SkeletonAction.MoveBone)) 185 m_Moved = false; 186 187 if (view.DoMoveBone(out var deltaPosition)) 188 { 189 if (!m_Moved) 190 { 191 skinningCache.BeginUndoOperation(TextContent.moveBone); 192 m_Moved = true; 193 } 194 195 m_Skeleton.MoveBones(rootBones, deltaPosition); 196 InvokePoseChanged(); 197 } 198 } 199 200 void HandleFreeMoveBone() 201 { 202 if (view.IsActionTriggering(SkeletonAction.FreeMoveBone)) 203 m_Moved = false; 204 205 if (view.DoFreeMoveBone(out var deltaPosition)) 206 { 207 if (!m_Moved) 208 { 209 skinningCache.BeginUndoOperation(TextContent.freeMoveBone); 210 m_Moved = true; 211 } 212 213 m_Skeleton.FreeMoveBones(selectedBones, deltaPosition); 214 InvokePoseChanged(); 215 } 216 } 217 218 void HandleMoveJoint() 219 { 220 if (view.IsActionTriggering(SkeletonAction.MoveJoint)) 221 m_Moved = false; 222 223 if (view.IsActionFinishing(SkeletonAction.MoveJoint)) 224 { 225 if (hoveredTail != null && hoveredTail.chainedChild == null && hotBone.parent == hoveredTail) 226 hoveredTail.chainedChild = hotBone; 227 } 228 229 if (view.DoMoveJoint(out var deltaPosition)) 230 { 231 if (!m_Moved) 232 { 233 skinningCache.BeginUndoOperation(TextContent.moveJoint); 234 m_Moved = true; 235 } 236 237 //Snap to parent endPosition 238 if (hoveredTail != null && hoveredTail.chainedChild == null && hotBone.parent == hoveredTail) 239 deltaPosition = hoveredTail.endPosition - hotBone.position; 240 241 m_Skeleton.MoveJoints(selectedBones, deltaPosition); 242 InvokePoseChanged(); 243 } 244 } 245 246 void HandleMoveEndPosition() 247 { 248 if (view.IsActionTriggering(SkeletonAction.MoveEndPosition)) 249 m_Moved = false; 250 251 if (view.IsActionFinishing(SkeletonAction.MoveEndPosition)) 252 { 253 if (hoveredJoint != null && hoveredJoint.parent == hotBone) 254 hotBone.chainedChild = hoveredJoint; 255 } 256 257 if (view.DoMoveEndPosition(out var endPosition)) 258 { 259 if (!m_Moved) 260 { 261 skinningCache.BeginUndoOperation(TextContent.moveEndPoint); 262 m_Moved = true; 263 } 264 265 Debug.Assert(hotBone != null); 266 Debug.Assert(hotBone.chainedChild == null); 267 268 if (hoveredJoint != null && hoveredJoint.parent == hotBone) 269 endPosition = hoveredJoint.position; 270 271 m_Skeleton.SetEndPosition(hotBone, endPosition); 272 InvokePoseChanged(); 273 } 274 } 275 276 void HandleChangeLength() 277 { 278 if (view.IsActionTriggering(SkeletonAction.ChangeLength)) 279 m_Moved = false; 280 281 if (view.DoChangeLength(out var endPosition)) 282 { 283 if (!m_Moved) 284 { 285 skinningCache.BeginUndoOperation(TextContent.boneLength); 286 m_Moved = true; 287 } 288 289 Debug.Assert(hotBone != null); 290 291 var direction = (Vector3)endPosition - hotBone.position; 292 hotBone.length = Vector3.Dot(direction, hotBone.right); 293 294 InvokePoseChanged(); 295 } 296 } 297 298 void HandleCreateBone() 299 { 300 if (view.DoCreateBoneStart(out var position)) 301 { 302 m_PrevCreatedBone = null; 303 304 if (hoveredTail != null) 305 { 306 m_PrevCreatedBone = hoveredTail; 307 m_CreateBoneStartPosition = hoveredTail.endPosition; 308 } 309 else 310 { 311 m_CreateBoneStartPosition = position; 312 } 313 } 314 315 if (view.DoCreateBone(out position)) 316 { 317 using (skinningCache.UndoScope(TextContent.createBone)) 318 { 319 var isChained = m_PrevCreatedBone != null; 320 var parentBone = isChained ? m_PrevCreatedBone : rootBone; 321 322 if (isChained) 323 m_CreateBoneStartPosition = m_PrevCreatedBone.endPosition; 324 325 var name = AutoBoneName(parentBone, skeleton.bones); 326 var bone = m_Skeleton.CreateBone(parentBone, m_CreateBoneStartPosition, position, isChained, name); 327 328 m_PrevCreatedBone = bone; 329 m_CreateBoneStartPosition = bone.endPosition; 330 331 InvokeTopologyChanged(); 332 InvokePoseChanged(); 333 } 334 } 335 } 336 337 void HandleSplitBone() 338 { 339 if (view.DoSplitBone(out var instanceID, out var position)) 340 { 341 using (skinningCache.UndoScope(TextContent.splitBone)) 342 { 343 var boneToSplit = GetBone(instanceID); 344 345 Debug.Assert(boneToSplit != null); 346 347 var splitLength = Vector3.Dot(hoveredBone.right, position - boneToSplit.position); 348 var name = AutoBoneName(boneToSplit, skeleton.bones); 349 350 m_Skeleton.SplitBone(boneToSplit, splitLength, name); 351 352 InvokeTopologyChanged(); 353 InvokePoseChanged(); 354 } 355 } 356 } 357 358 void HandleRemoveBone() 359 { 360 if (view.DoRemoveBone()) 361 { 362 using (skinningCache.UndoScope(TextContent.removeBone)) 363 { 364 m_Skeleton.DestroyBones(selectedBones); 365 366 selection.Clear(); 367 skinningCache.events.boneSelectionChanged.Invoke(); 368 InvokeTopologyChanged(); 369 InvokePoseChanged(); 370 } 371 } 372 } 373 374 void HandleCancelMultiStepAction() 375 { 376 if (view.DoCancelMultistepAction(false)) 377 m_PrevCreatedBone = null; 378 } 379 380 void DrawSkeleton() 381 { 382 if (!view.IsRepainting()) 383 return; 384 385 var isNotOnVisualElement = !skinningCache.IsOnVisualElement(); 386 if (view.IsActionActive(SkeletonAction.CreateBone) || view.IsActionHot(SkeletonAction.CreateBone)) 387 { 388 if (isNotOnVisualElement) 389 { 390 var endPoint = view.GetMouseWorldPosition(Vector3.forward, Vector3.zero); 391 392 if (view.IsActionHot(SkeletonAction.CreateBone)) 393 endPoint = m_CreateBoneStartPosition; 394 395 if (m_PrevCreatedBone == null && hoveredTail == null) 396 { 397 var root = rootBone; 398 if (root != null) 399 view.DrawBoneParentLink(root.position, endPoint, Vector3.forward, style.GetParentLinkPreviewColor(skeleton.boneCount)); 400 } 401 } 402 } 403 404 for (var i = 0; i < skeleton.boneCount; ++i) 405 { 406 var bone = skeleton.GetBone(i); 407 408 if (bone.isVisible == false || bone.parentBone == null || bone.parentBone.chainedChild == bone) 409 continue; 410 411 view.DrawBoneParentLink(bone.parent.position, bone.position, Vector3.forward, style.GetParentLinkColor(bone)); 412 } 413 414 for (var i = 0; i < skeleton.boneCount; ++i) 415 { 416 var bone = skeleton.GetBone(i); 417 418 if ((view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone == bone && isNotOnVisualElement) || bone.isVisible == false) 419 continue; 420 421 var isSelected = selection.Contains(bone.ToCharacterIfNeeded()); 422 var isHovered = hoveredBody == bone && view.IsActionHot(SkeletonAction.None) && isNotOnVisualElement; 423 424 DrawBoneOutline(bone, style.GetOutlineColor(bone, isSelected, isHovered), style.GetOutlineScale(isSelected)); 425 } 426 427 for (var i = 0; i < skeleton.boneCount; ++i) 428 { 429 var bone = skeleton.GetBone(i); 430 431 if ((view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone == bone && isNotOnVisualElement) || bone.isVisible == false) 432 continue; 433 434 DrawBone(bone, style.GetColor(bone)); 435 } 436 } 437 438 void DrawBone(BoneCache bone, Color color) 439 { 440 var isSelected = selection.Contains(bone.ToCharacterIfNeeded()); 441 var isNotOnVisualElement = !skinningCache.IsOnVisualElement(); 442 var isJointHovered = view.IsActionHot(SkeletonAction.None) && hoveredJoint == bone && isNotOnVisualElement; 443 var isTailHovered = view.IsActionHot(SkeletonAction.None) && hoveredTail == bone && isNotOnVisualElement; 444 445 view.DrawBone(bone.position, bone.right, Vector3.forward, bone.length, color, bone.chainedChild != null, isSelected, isJointHovered, isTailHovered, bone == hotBone); 446 } 447 448 void DrawBoneOutline(BoneCache bone, Color color, float outlineScale) 449 { 450 view.DrawBoneOutline(bone.position, bone.right, Vector3.forward, bone.length, color, outlineScale); 451 } 452 453 void DrawSplitBonePreview() 454 { 455 if (!view.IsRepainting()) 456 return; 457 458 if (skinningCache.IsOnVisualElement()) 459 return; 460 461 if (view.IsActionActive(SkeletonAction.SplitBone) && hoveredBone != null) 462 { 463 var splitLength = Vector3.Dot(hoveredBone.right, view.GetMouseWorldPosition(hoveredBone.forward, hoveredBody.position) - hoveredBone.position); 464 var position = hoveredBone.position + hoveredBone.right * splitLength; 465 var length = hoveredBone.length - splitLength; 466 var isSelected = selection.Contains(hoveredBone.ToCharacterIfNeeded()); 467 468 { 469 var color = style.GetOutlineColor(hoveredBone, false, false); 470 if (color.a > 0f) 471 view.DrawBoneOutline(hoveredBone.position, hoveredBone.right, Vector3.forward, splitLength, style.GetOutlineColor(hoveredBone, isSelected, true), style.GetOutlineScale(false)); 472 473 } 474 { 475 var color = style.GetPreviewOutlineColor(skeleton.boneCount); 476 if (color.a > 0f) 477 view.DrawBoneOutline(position, hoveredBone.right, Vector3.forward, length, style.GetPreviewOutlineColor(skeleton.boneCount), style.GetOutlineScale(false)); 478 479 } 480 481 view.DrawBone(hoveredBone.position, 482 hoveredBone.right, 483 Vector3.forward, 484 splitLength, 485 style.GetColor(hoveredBone), 486 hoveredBone.chainedChild != null, 487 false, false, false, false); 488 view.DrawBone(position, 489 hoveredBone.right, 490 Vector3.forward, 491 length, 492 style.GetPreviewColor(skeleton.boneCount), 493 hoveredBone.chainedChild != null, 494 false, false, false, false); 495 } 496 } 497 498 void DrawCreateBonePreview() 499 { 500 if (!view.IsRepainting()) 501 return; 502 503 if (skinningCache.IsOnVisualElement()) 504 return; 505 506 var color = style.GetPreviewColor(skeleton.boneCount); 507 var outlineColor = style.GetPreviewOutlineColor(skeleton.boneCount); 508 509 var startPosition = m_CreateBoneStartPosition; 510 var mousePosition = view.GetMouseWorldPosition(Vector3.forward, Vector3.zero); 511 512 if (view.IsActionActive(SkeletonAction.CreateBone)) 513 { 514 startPosition = mousePosition; 515 516 if (hoveredTail != null) 517 startPosition = hoveredTail.endPosition; 518 519 if (outlineColor.a > 0f) 520 view.DrawBoneOutline(startPosition, Vector3.right, Vector3.forward, 0f, outlineColor, style.GetOutlineScale(false)); 521 522 view.DrawBone(startPosition, Vector3.right, Vector3.forward, 0f, color, false, false, false, false, false); 523 } 524 525 if (view.IsActionHot(SkeletonAction.CreateBone)) 526 { 527 var direction = (mousePosition - startPosition); 528 529 if (outlineColor.a > 0f) 530 view.DrawBoneOutline(startPosition, direction.normalized, Vector3.forward, direction.magnitude, outlineColor, style.GetOutlineScale(false)); 531 532 view.DrawBone(startPosition, direction.normalized, Vector3.forward, direction.magnitude, color, false, false, false, false, false); 533 } 534 } 535 536 void DrawCursors() 537 { 538 if (!view.IsRepainting()) 539 return; 540 541 view.DrawCursors(!skinningCache.IsOnVisualElement()); 542 } 543 544 public static string AutoBoneName(BoneCache parent, IEnumerable<BoneCache> bones) 545 { 546 var parentName = "root"; 547 548 if (parent != null) 549 parentName = parent.name; 550 551 DissectBoneName(parentName, out var inheritedName, out _); 552 int nameCounter = FindBiggestNameCounter(bones); 553 554 if (inheritedName == k_DefaultRootName) 555 inheritedName = k_DefaultBoneName; 556 557 return $"{inheritedName}_{++nameCounter}"; 558 } 559 560 public static string AutoNameBoneCopy(string originalBoneName, IEnumerable<BoneCache> bones) 561 { 562 DissectBoneName(originalBoneName, out var inheritedName, out _); 563 int nameCounter = FindBiggestNameCounterForBone(inheritedName, bones); 564 565 if (inheritedName == k_DefaultRootName) 566 inheritedName = k_DefaultBoneName; 567 568 return $"{inheritedName}_{++nameCounter}"; 569 } 570 571 static int FindBiggestNameCounter(IEnumerable<BoneCache> bones) 572 { 573 var autoNameCounter = 0; 574 foreach (var bone in bones) 575 { 576 DissectBoneName(bone.name, out _, out var counter); 577 if (counter > autoNameCounter) 578 autoNameCounter = counter; 579 } 580 581 return autoNameCounter; 582 } 583 584 static int FindBiggestNameCounterForBone(string boneName, IEnumerable<BoneCache> bones) 585 { 586 var autoNameCounter = 0; 587 foreach (var bone in bones) 588 { 589 DissectBoneName(bone.name, out var inheritedName, out var counter); 590 { 591 if (inheritedName == boneName) 592 { 593 if (counter > autoNameCounter) 594 autoNameCounter = counter; 595 } 596 } 597 } 598 599 return autoNameCounter; 600 } 601 602 static void DissectBoneName(string boneName, out string inheritedName, out int counter) 603 { 604 if (IsBoneNameMatchAutoFormat(boneName)) 605 { 606 var tokens = boneName.Split('_'); 607 var lastTokenIndex = tokens.Length - 1; 608 609 var tokensWithoutLast = new string[lastTokenIndex]; 610 Array.Copy(tokens, tokensWithoutLast, lastTokenIndex); 611 inheritedName = string.Join("_", tokensWithoutLast); 612 counter = int.Parse(tokens[lastTokenIndex]); 613 } 614 else 615 { 616 inheritedName = boneName; 617 counter = -1; 618 } 619 } 620 621 static bool IsBoneNameMatchAutoFormat(string boneName) 622 { 623 return s_Regex.IsMatch(boneName); 624 } 625 626 void InvokeTopologyChanged() 627 { 628 skinningCache.events.skeletonTopologyChanged.Invoke(skeleton); 629 } 630 631 internal void InvokePoseChanged() 632 { 633 skeleton.SetPosePreview(); 634 635 if (editBindPose) 636 { 637 skeleton.SetDefaultPose(); 638 skinningCache.events.skeletonBindPoseChanged.Invoke(skeleton); 639 } 640 else 641 skinningCache.events.skeletonPreviewPoseChanged.Invoke(skeleton); 642 } 643 } 644}