#if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST #define ENABLE_ANIMATION_PERFORMANCE #endif using System; using System.Collections.Generic; using UnityEngine; using Unity.Collections; using System.Linq; using UnityEditor.U2D.Sprites; using UnityEngine.U2D.Animation; using UnityEngine.Rendering; using UnityEngine.U2D; namespace UnityEditor.U2D.Animation { internal class SpritePostProcess : AssetPostprocessor { void OnPreprocessAsset() { var dataProvider = GetSpriteEditorDataProvider(assetPath); if (dataProvider != null) InjectMainSkeletonBones(dataProvider); } void OnPostprocessSprites(Texture2D texture, Sprite[] sprites) { var ai = GetSpriteEditorDataProvider(assetPath); if (ai != null) { // Injecting these bones a second time, because the Sprite Rect positions // might have updated between OnPreprocessAsset and OnPostprocessSprites. InjectMainSkeletonBones(ai); var definitionScale = CalculateDefinitionScale(texture, ai.GetDataProvider()); ai.InitSpriteEditorDataProvider(); PostProcessBoneData(ai, definitionScale, sprites); PostProcessSpriteMeshData(ai, definitionScale, sprites); BoneGizmo.instance.ClearSpriteBoneCache(); } // Get all SpriteSkin in scene and inform them to refresh their cache RefreshSpriteSkinCache(); } static void InjectMainSkeletonBones(ISpriteEditorDataProvider dataProvider) { var characterDataProvider = dataProvider.GetDataProvider(); var mainSkeletonBonesDataProvider = dataProvider.GetDataProvider(); if (characterDataProvider == null || mainSkeletonBonesDataProvider == null) return; var skinningCache = Cache.Create(); skinningCache.Create(dataProvider, new SkinningCachePersistentStateTemp()); var skeletonBones = mainSkeletonBonesDataProvider.GetMainSkeletonData().bones ?? new SpriteBone[0]; RemapCharacterPartsToNewBones(skinningCache, skeletonBones); SkinningModule.ApplyChanges(skinningCache, dataProvider); } static void RemapCharacterPartsToNewBones(SkinningCache skinningCache, SpriteBone[] newBones) { var skeleton = skinningCache.character.skeleton; var previousStateBones = skeleton.bones; var skeletonBones = skinningCache.CreateBoneCacheFromSpriteBones(newBones, 1.0f); skeleton.SetBones(skeletonBones); for (var i = 0; i < skinningCache.character.parts.Length; i++) { var characterPart = skinningCache.character.parts[i]; var useGuids = !skeletonBones.All(newBone => previousStateBones.All(oldBone => newBone.guid != oldBone.guid)); characterPart.bones = useGuids ? characterPart.bones.Select(partBone => Array.Find(skeletonBones, skeletonBone => partBone.guid == skeletonBone.guid)).ToArray() : characterPart.bones.Select(partBone => skeletonBones.ElementAtOrDefault(Array.FindIndex(previousStateBones, oldBone => partBone.guid == oldBone.guid))).ToArray(); var mesh = skinningCache.GetMesh(characterPart.sprite); if (mesh != null) mesh.SetCompatibleBoneSet(characterPart.bones); skinningCache.character.parts[i] = characterPart; } } static void RefreshSpriteSkinCache() { #if ENABLE_ANIMATION_PERFORMANCE var spriteSkins = GameObject.FindObjectsOfType(); foreach (var ss in spriteSkins) { ss.ResetSprite(); } #endif } static void CalculateLocaltoWorldMatrix(int i, SpriteRect spriteRect, float definitionScale, float pixelsPerUnit, List spriteBone, ref UnityEngine.U2D.SpriteBone?[] outpriteBone, ref NativeArray bindPose) { if (outpriteBone[i] != null) return; UnityEngine.U2D.SpriteBone sp = spriteBone[i]; var isRoot = sp.parentId == -1; var position = isRoot ? (spriteBone[i].position - Vector3.Scale(spriteRect.rect.size, spriteRect.pivot)) : spriteBone[i].position; position.z = 0f; sp.position = position * definitionScale / pixelsPerUnit; sp.length = spriteBone[i].length * definitionScale / pixelsPerUnit; outpriteBone[i] = sp; // Calculate bind poses var worldPosition = Vector3.zero; var worldRotation = Quaternion.identity; if (sp.parentId == -1) { worldPosition = sp.position; worldRotation = sp.rotation; } else { if (outpriteBone[sp.parentId] == null) { CalculateLocaltoWorldMatrix(sp.parentId, spriteRect, definitionScale, pixelsPerUnit, spriteBone, ref outpriteBone, ref bindPose); } var parentBindPose = bindPose[sp.parentId]; var invParentBindPose = Matrix4x4.Inverse(parentBindPose); worldPosition = invParentBindPose.MultiplyPoint(sp.position); worldRotation = sp.rotation * invParentBindPose.rotation; } // Practically Matrix4x4.SetTRInverse var rot = Quaternion.Inverse(worldRotation); Matrix4x4 mat = Matrix4x4.identity; mat = Matrix4x4.Rotate(rot); mat = mat * Matrix4x4.Translate(-worldPosition); bindPose[i] = mat; } static bool PostProcessBoneData(ISpriteEditorDataProvider spriteDataProvider, float definitionScale, Sprite[] sprites) { var boneDataProvider = spriteDataProvider.GetDataProvider(); var textureDataProvider = spriteDataProvider.GetDataProvider(); if (sprites == null || sprites.Length == 0 || boneDataProvider == null || textureDataProvider == null) return false; var dataChanged = false; var spriteRects = spriteDataProvider.GetSpriteRects(); foreach (var sprite in sprites) { var guid = sprite.GetSpriteID(); { var spriteBone = boneDataProvider.GetBones(guid); if (spriteBone == null) continue; var spriteBoneCount = spriteBone.Count; if (spriteBoneCount == 0) continue; var spriteRect = spriteRects.First(s => { return s.spriteID == guid; }); var bindPose = new NativeArray(spriteBoneCount, Allocator.Temp); var outputSpriteBones = new UnityEngine.U2D.SpriteBone ? [spriteBoneCount]; for (int i = 0; i < spriteBoneCount; ++i) { CalculateLocaltoWorldMatrix(i, spriteRect, definitionScale, sprite.pixelsPerUnit, spriteBone, ref outputSpriteBones, ref bindPose); } sprite.SetBindPoses(bindPose); sprite.SetBones(outputSpriteBones.Select(x => x.Value).ToArray()); bindPose.Dispose(); dataChanged = true; } } return dataChanged; } static bool PostProcessSpriteMeshData(ISpriteEditorDataProvider spriteDataProvider, float definitionScale, Sprite[] sprites) { var spriteMeshDataProvider = spriteDataProvider.GetDataProvider(); var boneDataProvider = spriteDataProvider.GetDataProvider(); var textureDataProvider = spriteDataProvider.GetDataProvider(); if (sprites == null || sprites.Length == 0 || spriteMeshDataProvider == null || textureDataProvider == null) return false; var dataChanged = false; var spriteRects = spriteDataProvider.GetSpriteRects(); foreach (var sprite in sprites) { var guid = sprite.GetSpriteID(); var vertices = spriteMeshDataProvider.GetVertices(guid); int[] indices = null; if (vertices.Length > 2) indices = spriteMeshDataProvider.GetIndices(guid); if (indices != null && indices.Length > 2 && vertices.Length > 2 ) { var spriteRect = spriteRects.First(s => { return s.spriteID == guid; }); var spriteBone = boneDataProvider.GetBones(guid); var hasBones = spriteBone != null && spriteBone.Count > 0; var hasInvalidWeights = false; var vertexArray = new NativeArray(vertices.Length, Allocator.Temp); var boneWeightArray = new NativeArray(vertices.Length, Allocator.Temp); for (int i = 0; i < vertices.Length; ++i) { var boneWeight = vertices[i].boneWeight; vertexArray[i] = (Vector3)(vertices[i].position - Vector2.Scale(spriteRect.rect.size, spriteRect.pivot)) * definitionScale / sprite.pixelsPerUnit; boneWeightArray[i] = boneWeight; if (hasBones && !hasInvalidWeights) { var sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3; hasInvalidWeights = sum < 0.999f; } } var indicesArray = new NativeArray(indices.Length, Allocator.Temp); for (int i = 0; i < indices.Length; ++i) indicesArray[i] = (ushort)indices[i]; sprite.SetVertexCount(vertices.Length); sprite.SetVertexAttribute(VertexAttribute.Position, vertexArray); sprite.SetIndices(indicesArray); sprite.SetVertexAttribute(VertexAttribute.BlendWeight, boneWeightArray); vertexArray.Dispose(); boneWeightArray.Dispose(); indicesArray.Dispose(); // Deformed Sprites require proper Tangent Channels if Lit. Enable Tangent channels. if (hasBones) { var tangentArray = new NativeArray(vertices.Length, Allocator.Temp); for (int i = 0; i < vertices.Length; ++i) tangentArray[i] = new Vector4(1.0f, 0.0f, 0, -1.0f); sprite.SetVertexAttribute(VertexAttribute.Tangent, tangentArray); tangentArray.Dispose(); } dataChanged = true; if (hasBones && hasInvalidWeights) Debug.LogWarning("Sprite \"" + spriteRect.name + "\" contains bone weights which sum zero or are not normalized. To avoid visual artifacts please consider fixing them."); } else { var boneWeightArray = new NativeArray(sprite.GetVertexCount(), Allocator.Temp); var defaultBoneWeight = new BoneWeight() { weight0 = 1f }; for (var i = 0; i < boneWeightArray.Length; ++i) boneWeightArray[i] = defaultBoneWeight; sprite.SetVertexAttribute(VertexAttribute.BlendWeight, boneWeightArray); } } return dataChanged; } static float CalculateDefinitionScale(Texture2D texture, ITextureDataProvider dataProvider) { float definitionScale = 1; if (texture != null && dataProvider != null) { int actualWidth = 0, actualHeight = 0; dataProvider.GetTextureActualWidthAndHeight(out actualWidth, out actualHeight); float definitionScaleW = texture.width / (float)actualWidth; float definitionScaleH = texture.height / (float)actualHeight; definitionScale = Mathf.Min(definitionScaleW, definitionScaleH); } return definitionScale; } static ISpriteEditorDataProvider GetSpriteEditorDataProvider(string assetPath) { var dataProviderFactories = new SpriteDataProviderFactories(); dataProviderFactories.Init(); return dataProviderFactories.GetSpriteEditorDataProviderFromObject(AssetImporter.GetAtPath(assetPath)); } internal class SkinningCachePersistentStateTemp : ISkinningCachePersistentState { private string _lastSpriteId; private Tools _lastUsedTool; private List _lastBoneSelectionIds = null; private Texture2D _lastTexture = null; private SerializableDictionary _lastPreviewPose = null; private SerializableDictionary _lastBoneVisibility = null; private SerializableDictionary _lastBoneExpansion = null; private SerializableDictionary _lastSpriteVisibility = null; private SerializableDictionary _lastGroupVisibility = null; private SkinningMode _lastMode; private bool _lastVisibilityToolActive; private int _lastVisibilityToolIndex; private IndexedSelection _lastVertexSelection = null; private float _lastBrushSize; private float _lastBrushHardness; private float _lastBrushStep; string ISkinningCachePersistentState.lastSpriteId { get => _lastSpriteId; set => _lastSpriteId = value; } Tools ISkinningCachePersistentState.lastUsedTool { get => _lastUsedTool; set => _lastUsedTool = value; } List ISkinningCachePersistentState.lastBoneSelectionIds => _lastBoneSelectionIds; Texture2D ISkinningCachePersistentState.lastTexture { get => _lastTexture; set => _lastTexture = value; } SerializableDictionary ISkinningCachePersistentState.lastPreviewPose => _lastPreviewPose; SerializableDictionary ISkinningCachePersistentState.lastBoneVisibility => _lastBoneVisibility; SerializableDictionary ISkinningCachePersistentState.lastBoneExpansion => _lastBoneExpansion; SerializableDictionary ISkinningCachePersistentState.lastSpriteVisibility => _lastSpriteVisibility; SerializableDictionary ISkinningCachePersistentState.lastGroupVisibility => _lastGroupVisibility; SkinningMode ISkinningCachePersistentState.lastMode { get => _lastMode; set => _lastMode = value; } bool ISkinningCachePersistentState.lastVisibilityToolActive { get => _lastVisibilityToolActive; set => _lastVisibilityToolActive = value; } int ISkinningCachePersistentState.lastVisibilityToolIndex { get => _lastVisibilityToolIndex; set => _lastVisibilityToolIndex = value; } IndexedSelection ISkinningCachePersistentState.lastVertexSelection => _lastVertexSelection; float ISkinningCachePersistentState.lastBrushSize { get => _lastBrushSize; set => _lastBrushSize = value; } float ISkinningCachePersistentState.lastBrushHardness { get => _lastBrushHardness; set => _lastBrushHardness = value; } float ISkinningCachePersistentState.lastBrushStep { get => _lastBrushStep; set => _lastBrushStep = value; } } } }