using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEditor.U2D.Animation { internal static class SpriteCacheExtensions { public static MeshCache GetMesh(this SpriteCache sprite) { if (sprite != null) return sprite.skinningCache.GetMesh(sprite); return null; } public static MeshPreviewCache GetMeshPreview(this SpriteCache sprite) { if (sprite != null) return sprite.skinningCache.GetMeshPreview(sprite); return null; } public static SkeletonCache GetSkeleton(this SpriteCache sprite) { if (sprite != null) return sprite.skinningCache.GetSkeleton(sprite); return null; } public static CharacterPartCache GetCharacterPart(this SpriteCache sprite) { if (sprite != null) return sprite.skinningCache.GetCharacterPart(sprite); return null; } public static bool IsVisible(this SpriteCache sprite) { var isVisible = true; var characterPart = sprite.GetCharacterPart(); if (sprite.skinningCache.mode == SkinningMode.Character && characterPart != null) isVisible = characterPart.isVisible; return isVisible; } public static Matrix4x4 GetLocalToWorldMatrixFromMode(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; if (skinningCache.mode == SkinningMode.SpriteSheet) return sprite.localToWorldMatrix; var characterPart = sprite.GetCharacterPart(); Debug.Assert(characterPart != null); return characterPart.localToWorldMatrix; } public static BoneCache[] GetBonesFromMode(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; if (skinningCache.mode == SkinningMode.SpriteSheet) return sprite.GetSkeleton().bones; var characterPart = sprite.GetCharacterPart(); Debug.Assert(characterPart != null); return characterPart.bones; } public static void UpdateMesh(this SpriteCache sprite, BoneCache[] bones) { var mesh = sprite.GetMesh(); var previewMesh = sprite.GetMeshPreview(); Debug.Assert(mesh != null); Debug.Assert(previewMesh != null); mesh.bones = bones; previewMesh.SetWeightsDirty(); } public static void SmoothFill(this SpriteCache sprite) { var mesh = sprite.GetMesh(); if (mesh == null) return; var controller = new SpriteMeshDataController(); controller.spriteMeshData = mesh; controller.SmoothFill(); } public static void RestoreBindPose(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; var skeleton = sprite.GetSkeleton(); Debug.Assert(skeleton != null); skeleton.RestoreDefaultPose(); skinningCache.events.skeletonPreviewPoseChanged.Invoke(skeleton); } public static bool AssociateAllBones(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; if (skinningCache.mode == SkinningMode.SpriteSheet) return false; var character = skinningCache.character; Debug.Assert(character != null); Debug.Assert(character.skeleton != null); var characterPart = sprite.GetCharacterPart(); Debug.Assert(characterPart != null); var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray(); characterPart.bones = bones; characterPart.sprite.UpdateMesh(bones); return true; } public static bool AssociatePossibleBones(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; if (skinningCache.mode == SkinningMode.SpriteSheet) return false; var character = skinningCache.character; Debug.Assert(character != null); Debug.Assert(character.skeleton != null); var characterPart = sprite.GetCharacterPart(); Debug.Assert(characterPart != null); var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray(); var possibleBones = new List(); // check if any of the bones overlapped BoneCache shortestBoneDistance = null; var minDistances = float.MaxValue; var characterSpriteRect = new Rect(characterPart.position.x , characterPart.position.y, characterPart.sprite.textureRect.width, characterPart.sprite.textureRect.height); foreach (var bone in bones) { var startPoint = bone.position; var endPoint = bone.endPosition; if (IntersectsSegment(characterSpriteRect, startPoint, endPoint)) possibleBones.Add(bone); if (possibleBones.Count == 0) { // compare bone start end with rect's 4 line // compare rect point with bone line var points = new Vector2[] { startPoint, endPoint }; var rectLinePoints = new [] { new Vector2Int(0, 1), new Vector2Int(0, 2), new Vector2Int(1, 3), new Vector2Int(2, 3), }; var rectPoints = new [] { new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMin), new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMax), new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMin), new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMax) }; foreach (var point in points) { foreach (var rectLine in rectLinePoints) { var distance = PointToLineSegmentDistance(point, rectPoints[rectLine.x], rectPoints[rectLine.y]); if (distance < minDistances) { minDistances = distance; shortestBoneDistance = bone; } } } foreach (var rectPoint in rectPoints) { var distance = PointToLineSegmentDistance(rectPoint, startPoint, endPoint); if (distance < minDistances) { minDistances = distance; shortestBoneDistance = bone; } } } } // if none overlapped, we use the bone that is closest to us if (possibleBones.Count == 0 && shortestBoneDistance != null) { possibleBones.Add(shortestBoneDistance); } characterPart.bones = possibleBones.ToArray(); characterPart.sprite.UpdateMesh(possibleBones.ToArray()); return true; } static float PointToLineSegmentDistance(Vector2 p, Vector2 a, Vector2 b) { Vector2 n = b - a; Vector2 pa = a - p; float c = Vector2.Dot(n, pa); // Closest point is a if (c > 0.0f) return Vector2.Dot(pa, pa); Vector2 bp = p - b; // Closest point is b if (Vector2.Dot(n, bp) > 0.0f) return Vector2.Dot(bp, bp); // Closest point is between a and b Vector2 e = pa - n * (c / Vector2.Dot(n, n)); return Vector2.Dot( e, e ); } static bool IntersectsSegment(Rect rect, Vector2 p1, Vector2 p2) { float minX = Mathf.Min(p1.x, p2.x); float maxX = Mathf.Max(p1.x, p2.x); if (maxX > rect.xMax) { maxX = rect.xMax; } if (minX < rect.xMin) { minX = rect.xMin; } if (minX > maxX) { return false; } float minY = Mathf.Min(p1.y, p2.y); float maxY = Mathf.Max(p1.y, p2.y); float dx = p2.x - p1.x; if (Mathf.Abs(dx) > float.Epsilon) { float a = (p2.y - p1.y) / dx; float b = p1.y - a * p1.x; minY = a * minX + b; maxY = a * maxX + b; } if (minY > maxY) { float tmp = maxY; maxY = minY; minY = tmp; } if (maxY > rect.yMax) { maxY = rect.yMax; } if (minY < rect.yMin) { minY = rect.yMin; } if (minY > maxY) { return false; } return true; } public static void DeassociateUnusedBones(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; Debug.Assert(skinningCache.mode == SkinningMode.Character); var characterPart = sprite.GetCharacterPart(); Debug.Assert(characterPart != null); characterPart.DisassociateUnusedBones(); } public static void DeassociateAllBones(this SpriteCache sprite) { var skinningCache = sprite.skinningCache; if (skinningCache.mode == SkinningMode.SpriteSheet) return; var part = sprite.GetCharacterPart(); if (part.bones.Length == 0) return; Debug.Assert(part.sprite != null); part.bones = new BoneCache[0]; part.sprite.UpdateMesh(part.bones); skinningCache.events.characterPartChanged.Invoke(part); } } }