374 lines
16 KiB
C#
374 lines
16 KiB
C#
#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<ITextureDataProvider>());
|
|
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<ICharacterDataProvider>();
|
|
var mainSkeletonBonesDataProvider = dataProvider.GetDataProvider<IMainSkeletonDataProvider>();
|
|
if (characterDataProvider == null || mainSkeletonBonesDataProvider == null)
|
|
return;
|
|
|
|
var skinningCache = Cache.Create<SkinningCache>();
|
|
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<SpriteSkin>();
|
|
foreach (var ss in spriteSkins)
|
|
{
|
|
ss.ResetSprite();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void CalculateLocaltoWorldMatrix(int i, SpriteRect spriteRect, float definitionScale, float pixelsPerUnit, List<UnityEngine.U2D.SpriteBone> spriteBone, ref UnityEngine.U2D.SpriteBone?[] outpriteBone, ref NativeArray<Matrix4x4> 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<ISpriteBoneDataProvider>();
|
|
var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();
|
|
|
|
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<Matrix4x4>(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<ISpriteMeshDataProvider>();
|
|
var boneDataProvider = spriteDataProvider.GetDataProvider<ISpriteBoneDataProvider>();
|
|
var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();
|
|
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<Vector3>(vertices.Length, Allocator.Temp);
|
|
var boneWeightArray = new NativeArray<BoneWeight>(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<ushort>(indices.Length, Allocator.Temp);
|
|
for (int i = 0; i < indices.Length; ++i)
|
|
indicesArray[i] = (ushort)indices[i];
|
|
|
|
sprite.SetVertexCount(vertices.Length);
|
|
sprite.SetVertexAttribute<Vector3>(VertexAttribute.Position, vertexArray);
|
|
sprite.SetIndices(indicesArray);
|
|
sprite.SetVertexAttribute<BoneWeight>(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<Vector4>(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<Vector4>(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<BoneWeight>(sprite.GetVertexCount(), Allocator.Temp);
|
|
var defaultBoneWeight = new BoneWeight() { weight0 = 1f };
|
|
|
|
for (var i = 0; i < boneWeightArray.Length; ++i)
|
|
boneWeightArray[i] = defaultBoneWeight;
|
|
|
|
sprite.SetVertexAttribute<BoneWeight>(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<int> _lastBoneSelectionIds = null;
|
|
private Texture2D _lastTexture = null;
|
|
private SerializableDictionary<int, BonePose> _lastPreviewPose = null;
|
|
private SerializableDictionary<int, bool> _lastBoneVisibility = null;
|
|
private SerializableDictionary<int, bool> _lastBoneExpansion = null;
|
|
private SerializableDictionary<string, bool> _lastSpriteVisibility = null;
|
|
private SerializableDictionary<int, bool> _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<int> ISkinningCachePersistentState.lastBoneSelectionIds => _lastBoneSelectionIds;
|
|
|
|
Texture2D ISkinningCachePersistentState.lastTexture
|
|
{
|
|
get => _lastTexture;
|
|
set => _lastTexture = value;
|
|
}
|
|
|
|
SerializableDictionary<int, BonePose> ISkinningCachePersistentState.lastPreviewPose => _lastPreviewPose;
|
|
|
|
SerializableDictionary<int, bool> ISkinningCachePersistentState.lastBoneVisibility => _lastBoneVisibility;
|
|
|
|
SerializableDictionary<int, bool> ISkinningCachePersistentState.lastBoneExpansion => _lastBoneExpansion;
|
|
|
|
SerializableDictionary<string, bool> ISkinningCachePersistentState.lastSpriteVisibility => _lastSpriteVisibility;
|
|
|
|
SerializableDictionary<int, bool> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|