SkeletonController.cs 22 KB

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