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