A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEngine;
4using Unity.Collections;
5using System.Linq;
6using UnityEditor.U2D.Sprites;
7using UnityEngine.U2D.Animation;
8using UnityEngine.Rendering;
9using UnityEngine.U2D;
10
11namespace UnityEditor.U2D.Animation
12{
13 internal class SpritePostProcess : AssetPostprocessor
14 {
15 void OnPreprocessAsset()
16 {
17 var dataProvider = GetSpriteEditorDataProvider(assetPath);
18 if (dataProvider != null)
19 InjectMainSkeletonBones(dataProvider);
20 }
21
22 void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
23 {
24 var ai = GetSpriteEditorDataProvider(assetPath);
25 if (ai != null)
26 {
27 // Injecting these bones a second time, because the Sprite Rect positions
28 // might have updated between OnPreprocessAsset and OnPostprocessSprites.
29 InjectMainSkeletonBones(ai);
30
31 var definitionScale = CalculateDefinitionScale(texture, ai.GetDataProvider<ITextureDataProvider>());
32 ai.InitSpriteEditorDataProvider();
33 PostProcessBoneData(ai, definitionScale, sprites);
34 PostProcessSpriteMeshData(ai, definitionScale, sprites, assetImporter);
35 BoneGizmo.instance.ClearSpriteBoneCache();
36 }
37
38 // Get all SpriteSkin in scene and inform them to refresh their cache
39 RefreshSpriteSkinCache();
40 }
41
42 static void InjectMainSkeletonBones(ISpriteEditorDataProvider dataProvider)
43 {
44 var characterDataProvider = dataProvider.GetDataProvider<ICharacterDataProvider>();
45 var mainSkeletonBonesDataProvider = dataProvider.GetDataProvider<IMainSkeletonDataProvider>();
46 if (characterDataProvider == null || mainSkeletonBonesDataProvider == null)
47 return;
48
49 var skinningCache = Cache.Create<SkinningCache>();
50 skinningCache.Create(dataProvider, new SkinningCachePersistentStateTemp());
51
52 var skeletonBones = mainSkeletonBonesDataProvider.GetMainSkeletonData().bones ?? new SpriteBone[0];
53 RemapCharacterPartsToNewBones(skinningCache, skeletonBones);
54
55 SkinningModule.ApplyChanges(skinningCache, dataProvider);
56 }
57
58 static void RemapCharacterPartsToNewBones(SkinningCache skinningCache, SpriteBone[] newBones)
59 {
60 var skeleton = skinningCache.character.skeleton;
61 var previousStateBones = skeleton.bones;
62 var skeletonBones = skinningCache.CreateBoneCacheFromSpriteBones(newBones, 1.0f);
63 skeleton.SetBones(skeletonBones);
64
65 for (var i = 0; i < skinningCache.character.parts.Length; i++)
66 {
67 var characterPart = skinningCache.character.parts[i];
68 var useGuids = !skeletonBones.All(newBone => previousStateBones.All(oldBone => newBone.guid != oldBone.guid));
69 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();
70
71 var mesh = skinningCache.GetMesh(characterPart.sprite);
72 if (mesh != null)
73 mesh.SetCompatibleBoneSet(characterPart.bones);
74
75 skinningCache.character.parts[i] = characterPart;
76 }
77 }
78
79 static void RefreshSpriteSkinCache()
80 {
81 var spriteSkins = GameObject.FindObjectsByType<SpriteSkin>(FindObjectsSortMode.None);
82 foreach (var ss in spriteSkins)
83 {
84 ss.ResetSprite();
85 }
86 }
87
88 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)
89 {
90 if (outpriteBone[i] != null)
91 return;
92 UnityEngine.U2D.SpriteBone sp = spriteBone[i];
93 var isRoot = sp.parentId == -1;
94 var position = isRoot ? (spriteBone[i].position - Vector3.Scale(spriteRect.rect.size, spriteRect.pivot)) : spriteBone[i].position;
95 position.z = 0f;
96 sp.position = position * definitionScale / pixelsPerUnit;
97 sp.length = spriteBone[i].length * definitionScale / pixelsPerUnit;
98 outpriteBone[i] = sp;
99
100 // Calculate bind poses
101 var worldPosition = Vector3.zero;
102 var worldRotation = Quaternion.identity;
103
104 if (sp.parentId == -1)
105 {
106 worldPosition = sp.position;
107 worldRotation = sp.rotation;
108 }
109 else
110 {
111 if (outpriteBone[sp.parentId] == null)
112 {
113 CalculateLocaltoWorldMatrix(sp.parentId, spriteRect, definitionScale, pixelsPerUnit, spriteBone, ref outpriteBone, ref bindPose);
114 }
115
116 var parentBindPose = bindPose[sp.parentId];
117 var invParentBindPose = Matrix4x4.Inverse(parentBindPose);
118
119 worldPosition = invParentBindPose.MultiplyPoint(sp.position);
120 worldRotation = sp.rotation * invParentBindPose.rotation;
121 }
122
123 // Practically Matrix4x4.SetTRInverse
124 var rot = Quaternion.Inverse(worldRotation);
125 Matrix4x4 mat = Matrix4x4.identity;
126 mat = Matrix4x4.Rotate(rot);
127 mat = mat * Matrix4x4.Translate(-worldPosition);
128
129
130 bindPose[i] = mat;
131 }
132
133 static bool PostProcessBoneData(ISpriteEditorDataProvider spriteDataProvider, float definitionScale, Sprite[] sprites)
134 {
135 var boneDataProvider = spriteDataProvider.GetDataProvider<ISpriteBoneDataProvider>();
136 var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();
137
138 if (sprites == null || sprites.Length == 0 || boneDataProvider == null || textureDataProvider == null)
139 return false;
140
141 var dataChanged = false;
142 var spriteRects = spriteDataProvider.GetSpriteRects();
143 foreach (var sprite in sprites)
144 {
145 var guid = sprite.GetSpriteID();
146 {
147 var spriteBone = boneDataProvider.GetBones(guid);
148 if (spriteBone == null)
149 continue;
150
151 var spriteBoneCount = spriteBone.Count;
152 if (spriteBoneCount == 0)
153 continue;
154
155 var spriteRect = spriteRects.First(s => { return s.spriteID == guid; });
156
157 var bindPose = new NativeArray<Matrix4x4>(spriteBoneCount, Allocator.Temp);
158 var outputSpriteBones = new UnityEngine.U2D.SpriteBone? [spriteBoneCount];
159 for (int i = 0; i < spriteBoneCount; ++i)
160 {
161 CalculateLocaltoWorldMatrix(i, spriteRect, definitionScale, sprite.pixelsPerUnit, spriteBone, ref outputSpriteBones, ref bindPose);
162 }
163
164 sprite.SetBindPoses(bindPose);
165 sprite.SetBones(outputSpriteBones.Select(x => x.Value).ToArray());
166 bindPose.Dispose();
167
168 dataChanged = true;
169 }
170 }
171
172 return dataChanged;
173 }
174
175 static bool PostProcessSpriteMeshData(ISpriteEditorDataProvider spriteDataProvider, float definitionScale, Sprite[] sprites, AssetImporter assetImporter)
176 {
177 var spriteMeshDataProvider = spriteDataProvider.GetDataProvider<ISpriteMeshDataProvider>();
178 var boneDataProvider = spriteDataProvider.GetDataProvider<ISpriteBoneDataProvider>();
179 var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();
180 var outlineDataProvider = spriteDataProvider.GetDataProvider<ISpriteOutlineDataProvider>();
181 if (sprites == null || sprites.Length == 0 || spriteMeshDataProvider == null || textureDataProvider == null)
182 return false;
183
184 var dataChanged = false;
185 var spriteRects = spriteDataProvider.GetSpriteRects();
186 var showMeshOverwriteWarning = SkinningModuleSettings.showSpriteMeshOverwriteWarning;
187 foreach (var sprite in sprites)
188 {
189 var guid = sprite.GetSpriteID();
190 var vertices = spriteMeshDataProvider.GetVertices(guid);
191 int[] indices = null;
192 if (vertices.Length > 2)
193 indices = spriteMeshDataProvider.GetIndices(guid);
194
195 var spriteBone = boneDataProvider.GetBones(guid);
196 var hasBones = spriteBone is { Count: > 0 };
197
198 if (indices != null && indices.Length > 2 && vertices.Length > 2)
199 {
200 var spriteRect = spriteRects.First(s => { return s.spriteID == guid; });
201 var hasInvalidWeights = false;
202
203 var vertexArray = new NativeArray<Vector3>(vertices.Length, Allocator.Temp);
204 var boneWeightArray = new NativeArray<BoneWeight>(vertices.Length, Allocator.Temp);
205
206 for (int i = 0; i < vertices.Length; ++i)
207 {
208 var boneWeight = vertices[i].boneWeight;
209
210 vertexArray[i] = (Vector3)(vertices[i].position - Vector2.Scale(spriteRect.rect.size, spriteRect.pivot)) * definitionScale / sprite.pixelsPerUnit;
211 boneWeightArray[i] = boneWeight;
212
213 if (hasBones && !hasInvalidWeights)
214 {
215 var sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3;
216 hasInvalidWeights = sum < 0.999f;
217 }
218 }
219
220 var indicesArray = new NativeArray<ushort>(indices.Length, Allocator.Temp);
221 for (int i = 0; i < indices.Length; ++i)
222 indicesArray[i] = (ushort)indices[i];
223
224 if (showMeshOverwriteWarning && outlineDataProvider?.GetOutlines(guid)?.Count > 0)
225 Debug.LogWarning(string.Format(TextContent.spriteMeshOverwriteWarning, sprite.name), assetImporter);
226 sprite.SetVertexCount(vertices.Length);
227 sprite.SetVertexAttribute<Vector3>(VertexAttribute.Position, vertexArray);
228 sprite.SetIndices(indicesArray);
229 if (hasBones)
230 sprite.SetVertexAttribute<BoneWeight>(VertexAttribute.BlendWeight, boneWeightArray);
231 vertexArray.Dispose();
232 boneWeightArray.Dispose();
233 indicesArray.Dispose();
234
235 // Deformed Sprites require proper Tangent Channels if Lit. Enable Tangent channels.
236 if (hasBones)
237 {
238 var tangentArray = new NativeArray<Vector4>(vertices.Length, Allocator.Temp);
239 for (int i = 0; i < vertices.Length; ++i)
240 tangentArray[i] = new Vector4(1.0f, 0.0f, 0, -1.0f);
241 sprite.SetVertexAttribute<Vector4>(VertexAttribute.Tangent, tangentArray);
242 tangentArray.Dispose();
243 }
244
245 dataChanged = true;
246
247 if (hasBones && hasInvalidWeights)
248 Debug.LogWarning(string.Format(TextContent.boneWeightsNotSumZeroWarning, spriteRect.name), assetImporter);
249 }
250 else
251 {
252 if (hasBones)
253 {
254 var boneWeightArray = new NativeArray<BoneWeight>(sprite.GetVertexCount(), Allocator.Temp);
255 var defaultBoneWeight = new BoneWeight() { weight0 = 1f };
256
257 for (var i = 0; i < boneWeightArray.Length; ++i)
258 boneWeightArray[i] = defaultBoneWeight;
259
260 sprite.SetVertexAttribute<BoneWeight>(VertexAttribute.BlendWeight, boneWeightArray);
261 }
262 }
263 }
264
265 return dataChanged;
266 }
267
268 static float CalculateDefinitionScale(Texture2D texture, ITextureDataProvider dataProvider)
269 {
270 float definitionScale = 1;
271 if (texture != null && dataProvider != null)
272 {
273 int actualWidth = 0, actualHeight = 0;
274 dataProvider.GetTextureActualWidthAndHeight(out actualWidth, out actualHeight);
275 float definitionScaleW = texture.width / (float)actualWidth;
276 float definitionScaleH = texture.height / (float)actualHeight;
277 definitionScale = Mathf.Min(definitionScaleW, definitionScaleH);
278 }
279
280 return definitionScale;
281 }
282
283 static ISpriteEditorDataProvider GetSpriteEditorDataProvider(string assetPath)
284 {
285 var dataProviderFactories = new SpriteDataProviderFactories();
286 dataProviderFactories.Init();
287 return dataProviderFactories.GetSpriteEditorDataProviderFromObject(AssetImporter.GetAtPath(assetPath));
288 }
289
290 internal class SkinningCachePersistentStateTemp : ISkinningCachePersistentState
291 {
292 private string _lastSpriteId;
293 private Tools _lastUsedTool;
294 private List<int> _lastBoneSelectionIds = null;
295 private Texture2D _lastTexture = null;
296 private SerializableDictionary<int, BonePose> _lastPreviewPose = null;
297 private SerializableDictionary<int, bool> _lastBoneVisibility = null;
298 private SerializableDictionary<int, bool> _lastBoneExpansion = null;
299 private SerializableDictionary<string, bool> _lastSpriteVisibility = null;
300 private SerializableDictionary<int, bool> _lastGroupVisibility = null;
301 private SkinningMode _lastMode;
302 private bool _lastVisibilityToolActive;
303 private int _lastVisibilityToolIndex;
304 private IndexedSelection _lastVertexSelection = null;
305 private float _lastBrushSize;
306 private float _lastBrushHardness;
307 private float _lastBrushStep;
308
309 string ISkinningCachePersistentState.lastSpriteId
310 {
311 get => _lastSpriteId;
312 set => _lastSpriteId = value;
313 }
314
315 Tools ISkinningCachePersistentState.lastUsedTool
316 {
317 get => _lastUsedTool;
318 set => _lastUsedTool = value;
319 }
320
321 List<int> ISkinningCachePersistentState.lastBoneSelectionIds => _lastBoneSelectionIds;
322
323 Texture2D ISkinningCachePersistentState.lastTexture
324 {
325 get => _lastTexture;
326 set => _lastTexture = value;
327 }
328
329 SerializableDictionary<int, BonePose> ISkinningCachePersistentState.lastPreviewPose => _lastPreviewPose;
330
331 SerializableDictionary<int, bool> ISkinningCachePersistentState.lastBoneVisibility => _lastBoneVisibility;
332
333 SerializableDictionary<int, bool> ISkinningCachePersistentState.lastBoneExpansion => _lastBoneExpansion;
334
335 SerializableDictionary<string, bool> ISkinningCachePersistentState.lastSpriteVisibility => _lastSpriteVisibility;
336
337 SerializableDictionary<int, bool> ISkinningCachePersistentState.lastGroupVisibility => _lastGroupVisibility;
338
339 SkinningMode ISkinningCachePersistentState.lastMode
340 {
341 get => _lastMode;
342 set => _lastMode = value;
343 }
344
345 bool ISkinningCachePersistentState.lastVisibilityToolActive
346 {
347 get => _lastVisibilityToolActive;
348 set => _lastVisibilityToolActive = value;
349 }
350
351 int ISkinningCachePersistentState.lastVisibilityToolIndex
352 {
353 get => _lastVisibilityToolIndex;
354 set => _lastVisibilityToolIndex = value;
355 }
356
357 IndexedSelection ISkinningCachePersistentState.lastVertexSelection => _lastVertexSelection;
358
359 float ISkinningCachePersistentState.lastBrushSize
360 {
361 get => _lastBrushSize;
362 set => _lastBrushSize = value;
363 }
364
365 float ISkinningCachePersistentState.lastBrushHardness
366 {
367 get => _lastBrushHardness;
368 set => _lastBrushHardness = value;
369 }
370
371 float ISkinningCachePersistentState.lastBrushStep
372 {
373 get => _lastBrushStep;
374 set => _lastBrushStep = value;
375 }
376 }
377 }
378}