A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using Unity.Collections;
4using Unity.Jobs;
5using Unity.Mathematics;
6using UnityEditor.U2D.Common;
7using UnityEditor.U2D.Layout;
8using UnityEngine;
9
10namespace UnityEditor.U2D.Animation
11{
12 internal class GenerateGeometryTool : MeshToolWrapper
13 {
14 private const float kWeightTolerance = 0.1f;
15 private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
16 private ITriangulator m_Triangulator;
17 private IOutlineGenerator m_OutlineGenerator;
18 private IWeightsGenerator m_WeightGenerator;
19 private GenerateGeometryPanel m_GenerateGeometryPanel;
20
21 internal override void OnCreate()
22 {
23 m_Triangulator = new Triangulator();
24 m_OutlineGenerator = new OutlineGenerator();
25 m_WeightGenerator = new BoundedBiharmonicWeightsGenerator();
26 }
27
28 public override void Initialize(LayoutOverlay layout)
29 {
30 base.Initialize(layout);
31
32 m_GenerateGeometryPanel = GenerateGeometryPanel.GenerateFromUXML();
33 m_GenerateGeometryPanel.skinningCache = skinningCache;
34
35 layout.rightOverlay.Add(m_GenerateGeometryPanel);
36
37 BindElements();
38 Hide();
39 }
40
41 private void BindElements()
42 {
43 Debug.Assert(m_GenerateGeometryPanel != null);
44
45 m_GenerateGeometryPanel.onAutoGenerateGeometry += (float detail, byte alpha, float subdivide) =>
46 {
47 var selectedSprite = skinningCache.selectedSprite;
48 if (selectedSprite != null)
49 GenerateGeometryForSprites(new[] { selectedSprite }, detail, alpha, subdivide);
50 };
51
52 m_GenerateGeometryPanel.onAutoGenerateGeometryAll += (float detail, byte alpha, float subdivide) =>
53 {
54 var sprites = skinningCache.GetSprites();
55 GenerateGeometryForSprites(sprites, detail, alpha, subdivide);
56 };
57 }
58
59 void GenerateGeometryForSprites(SpriteCache[] sprites, float detail, byte alpha, float subdivide)
60 {
61 var cancelProgress = false;
62
63 using (skinningCache.UndoScope(TextContent.generateGeometry))
64 {
65
66 float progressMax = sprites.Length * 4; // for ProgressBar
67 int validSpriteCount = 0;
68
69 //
70 // Generate Outline
71 //
72 for (var i = 0; i < sprites.Length; ++i)
73 {
74 var sprite = sprites[i];
75 if (!sprite.IsVisible())
76 continue;
77
78 Debug.Assert(sprite != null);
79 var mesh = sprite.GetMesh();
80 Debug.Assert(mesh != null);
81
82 m_SpriteMeshDataController.spriteMeshData = mesh;
83 validSpriteCount++;
84
85 cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.generatingOutline, sprite.name, i / progressMax);
86 if (cancelProgress)
87 break;
88 m_SpriteMeshDataController.OutlineFromAlpha(m_OutlineGenerator, mesh.textureDataProvider, detail / 100f, alpha);
89 }
90
91 //
92 // Generate Base Mesh Threaded.
93 //
94 const int maxDataCount = 65536;
95 var spriteList = new List<SpriteJobData>();
96 var jobHandles = new NativeArray<JobHandle>(validSpriteCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
97 int jobCount = 0;
98
99 for (var i = 0; i < sprites.Length; ++i)
100 {
101 var sprite = sprites[i];
102 if (!sprite.IsVisible())
103 continue;
104
105 cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.triangulatingGeometry, sprite.name, 0.25f + (i / progressMax));
106 if (cancelProgress)
107 break;
108
109 var mesh = sprite.GetMesh();
110 m_SpriteMeshDataController.spriteMeshData = mesh;
111
112 SpriteJobData sd = new SpriteJobData();
113 sd.spriteMesh = mesh;
114 sd.vertices = new NativeArray<float2>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
115 sd.edges = new NativeArray<int2>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
116 sd.indices = new NativeArray<int>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
117 sd.weights = new NativeArray<BoneWeight>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
118 sd.result = new NativeArray<int4>(1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
119 sd.result[0] = int4.zero;
120 spriteList.Add(sd);
121 if (m_GenerateGeometryPanel.generateWeights)
122 {
123 jobHandles[jobCount] = m_SpriteMeshDataController.TriangulateJob(m_Triangulator, sd);
124 }
125 else
126 {
127 jobHandles[jobCount] = default(JobHandle);
128 m_SpriteMeshDataController.Triangulate(m_Triangulator);
129 }
130 jobCount++;
131 }
132 JobHandle.CombineDependencies(jobHandles).Complete();
133
134 //
135 // Generate Base Mesh Fallback.
136 //
137 for (var i = 0; i < spriteList.Count; i++)
138 {
139 var sd = spriteList[i];
140 if (math.all(sd.result[0].xy))
141 {
142 sd.spriteMesh.Clear();
143
144 var edges = new int2[sd.result[0].z];
145 var indices = new int[sd.result[0].y];
146
147 for (var j = 0; j < sd.result[0].x; ++j)
148 sd.spriteMesh.AddVertex(sd.vertices[j], default(BoneWeight));
149 for (var j = 0; j < sd.result[0].y; ++j)
150 indices[j] = sd.indices[j];
151 for (var j = 0; j < sd.result[0].z; ++j)
152 edges[j] = sd.edges[j];
153
154 sd.spriteMesh.SetEdges(edges);
155 sd.spriteMesh.SetIndices(indices);
156 }
157 else
158 {
159 m_SpriteMeshDataController.spriteMeshData = sd.spriteMesh;
160 m_SpriteMeshDataController.Triangulate(m_Triangulator);
161 }
162 }
163
164 //
165 // Subdivide.
166 //
167
168 jobCount = 0;
169 if (subdivide > 0f)
170 {
171 var largestAreaFactor = subdivide != 0 ? Mathf.Lerp(0.5f, 0.05f, Math.Min(subdivide, 100f) / 100f) : subdivide;
172
173 for (var i = 0; i < sprites.Length; ++i)
174 {
175 var sprite = sprites[i];
176 if (!sprite.IsVisible())
177 continue;
178
179 cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.subdividingGeometry, sprite.name, 0.5f + (i / progressMax));
180 if (cancelProgress)
181 break;
182
183 var mesh = sprite.GetMesh();
184 m_SpriteMeshDataController.spriteMeshData = mesh;
185
186 var sd = spriteList[i];
187 sd.spriteMesh = mesh;
188 sd.result[0] = int4.zero;
189 m_SpriteMeshDataController.Subdivide(m_Triangulator, sd, largestAreaFactor, 0f);
190 }
191
192 }
193
194 //
195 // Weight.
196 //
197 jobCount = 0;
198 if (m_GenerateGeometryPanel.generateWeights)
199 {
200
201 for (var i = 0; i < sprites.Length; i++)
202 {
203 var sprite = sprites[i];
204 if (!sprite.IsVisible())
205 continue;
206
207 var mesh = sprite.GetMesh();
208 m_SpriteMeshDataController.spriteMeshData = mesh;
209
210 cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.generatingWeights, sprite.name, 0.75f + (i / progressMax));
211 if (cancelProgress)
212 break;
213
214 var sd = spriteList[i];
215 jobHandles[jobCount] = GenerateWeights(sprite, sd);
216 jobCount++;
217 }
218
219 // Weight
220 JobHandle.CombineDependencies(jobHandles).Complete();
221
222 for (var i = 0; i < sprites.Length; i++)
223 {
224 var sprite = sprites[i];
225 if (!sprite.IsVisible())
226 continue;
227
228 var mesh = sprite.GetMesh();
229 m_SpriteMeshDataController.spriteMeshData = mesh;
230 var sd = spriteList[i];
231
232 for (var j = 0; j < mesh.vertexCount; ++j)
233 {
234 var editableBoneWeight = EditableBoneWeightUtility.CreateFromBoneWeight(sd.weights[j]);
235
236 if (kWeightTolerance > 0f)
237 {
238 editableBoneWeight.FilterChannels(kWeightTolerance);
239 editableBoneWeight.Normalize();
240 }
241
242 mesh.vertexWeights[j] = editableBoneWeight;
243 }
244 if (null != sprite.GetCharacterPart())
245 sprite.DeassociateUnusedBones();
246 m_SpriteMeshDataController.SortTrianglesByDepth();
247 }
248
249 }
250
251 for (var i = 0; i < spriteList.Count; i++)
252 {
253 var sd = spriteList[i];
254 sd.result.Dispose();
255 sd.indices.Dispose();
256 sd.edges.Dispose();
257 sd.vertices.Dispose();
258 sd.weights.Dispose();
259 }
260
261 if (!cancelProgress)
262 {
263 skinningCache.vertexSelection.Clear();
264 foreach(var sprite in sprites)
265 skinningCache.events.meshChanged.Invoke(sprite.GetMesh());
266 }
267
268 EditorUtility.ClearProgressBar();
269 }
270
271 if(cancelProgress)
272 Undo.PerformUndo();
273 }
274
275 protected override void OnActivate()
276 {
277 base.OnActivate();
278 UpdateButton();
279 Show();
280 skinningCache.events.selectedSpriteChanged.AddListener(OnSelectedSpriteChanged);
281 }
282
283 protected override void OnDeactivate()
284 {
285 base.OnDeactivate();
286 Hide();
287 skinningCache.events.selectedSpriteChanged.RemoveListener(OnSelectedSpriteChanged);
288 }
289
290 private void Show()
291 {
292 m_GenerateGeometryPanel.SetHiddenFromLayout(false);
293 }
294
295 private void Hide()
296 {
297 m_GenerateGeometryPanel.SetHiddenFromLayout(true);
298 }
299
300 private void UpdateButton()
301 {
302 var selectedSprite = skinningCache.selectedSprite;
303
304 if (selectedSprite == null)
305 m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Multiple);
306 else
307 m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Single);
308 }
309
310 private void OnSelectedSpriteChanged(SpriteCache sprite)
311 {
312 UpdateButton();
313 }
314
315 private JobHandle GenerateWeights(SpriteCache sprite, SpriteJobData sd)
316 {
317 Debug.Assert(sprite != null);
318
319 var mesh = sprite.GetMesh();
320
321 Debug.Assert(mesh != null);
322
323 using (new DefaultPoseScope(skinningCache.GetEffectiveSkeleton(sprite)))
324 {
325 sprite.AssociatePossibleBones();
326 return GenerateWeights(mesh, sd);
327 }
328 }
329
330 // todo: Remove. This function seems dubious. Only associate if boneCount is 0 or if boneCount 1 and first bone matches ?
331 private bool NeedsAssociateBones(CharacterPartCache characterPart)
332 {
333 if (characterPart == null)
334 return false;
335
336 var skeleton = characterPart.skinningCache.character.skeleton;
337
338 return characterPart.boneCount == 0 ||
339 (characterPart.boneCount == 1 && characterPart.GetBone(0) == skeleton.GetBone(0));
340 }
341
342 private JobHandle GenerateWeights(MeshCache mesh, SpriteJobData sd)
343 {
344 Debug.Assert(mesh != null);
345
346 m_SpriteMeshDataController.spriteMeshData = mesh;
347 var JobHandle = m_SpriteMeshDataController.CalculateWeightsJob(m_WeightGenerator, null, kWeightTolerance, sd);
348
349 return JobHandle;
350 }
351
352 protected override void OnGUI()
353 {
354 m_MeshPreviewBehaviour.showWeightMap = m_GenerateGeometryPanel.generateWeights;
355 m_MeshPreviewBehaviour.overlaySelected = m_GenerateGeometryPanel.generateWeights;
356
357 skeletonTool.skeletonStyle = SkeletonStyles.Default;
358
359 if (m_GenerateGeometryPanel.generateWeights)
360 skeletonTool.skeletonStyle = SkeletonStyles.WeightMap;
361
362 DoSkeletonGUI();
363 }
364 }
365}