A game about forced loneliness, made by TACStudios
1using System.Collections.Generic;
2using System.Linq;
3using UnityEngine;
4
5namespace UnityEditor.U2D.Animation
6{
7 internal static class SpriteCacheExtensions
8 {
9 public static MeshCache GetMesh(this SpriteCache sprite)
10 {
11 if (sprite != null)
12 return sprite.skinningCache.GetMesh(sprite);
13 return null;
14 }
15
16 public static MeshPreviewCache GetMeshPreview(this SpriteCache sprite)
17 {
18 if (sprite != null)
19 return sprite.skinningCache.GetMeshPreview(sprite);
20 return null;
21 }
22
23 public static SkeletonCache GetSkeleton(this SpriteCache sprite)
24 {
25 if (sprite != null)
26 return sprite.skinningCache.GetSkeleton(sprite);
27 return null;
28 }
29
30 public static CharacterPartCache GetCharacterPart(this SpriteCache sprite)
31 {
32 if (sprite != null)
33 return sprite.skinningCache.GetCharacterPart(sprite);
34 return null;
35 }
36
37 public static bool IsVisible(this SpriteCache sprite)
38 {
39 var isVisible = true;
40 var characterPart = sprite.GetCharacterPart();
41
42 if (sprite.skinningCache.mode == SkinningMode.Character && characterPart != null)
43 isVisible = characterPart.isVisible;
44
45 return isVisible;
46 }
47
48 public static Matrix4x4 GetLocalToWorldMatrixFromMode(this SpriteCache sprite)
49 {
50 var skinningCache = sprite.skinningCache;
51
52 if (skinningCache.mode == SkinningMode.SpriteSheet)
53 return sprite.localToWorldMatrix;
54
55 var characterPart = sprite.GetCharacterPart();
56
57 Debug.Assert(characterPart != null);
58
59 return characterPart.localToWorldMatrix;
60 }
61
62 public static BoneCache[] GetBonesFromMode(this SpriteCache sprite)
63 {
64 var skinningCache = sprite.skinningCache;
65
66 if (skinningCache.mode == SkinningMode.SpriteSheet)
67 return sprite.GetSkeleton().bones;
68
69 var characterPart = sprite.GetCharacterPart();
70 Debug.Assert(characterPart != null);
71 return characterPart.bones;
72 }
73
74 public static void UpdateMesh(this SpriteCache sprite, BoneCache[] bones)
75 {
76 var mesh = sprite.GetMesh();
77 var previewMesh = sprite.GetMeshPreview();
78
79 Debug.Assert(mesh != null);
80 Debug.Assert(previewMesh != null);
81
82 mesh.bones = bones;
83
84 previewMesh.SetWeightsDirty();
85 }
86
87 public static void SmoothFill(this SpriteCache sprite)
88 {
89 var mesh = sprite.GetMesh();
90
91 if (mesh == null)
92 return;
93
94 var controller = new SpriteMeshDataController();
95 controller.spriteMeshData = mesh;
96 controller.SmoothFill();
97 }
98
99 public static void RestoreBindPose(this SpriteCache sprite)
100 {
101 var skinningCache = sprite.skinningCache;
102 var skeleton = sprite.GetSkeleton();
103 Debug.Assert(skeleton != null);
104 skeleton.RestoreDefaultPose();
105 skinningCache.events.skeletonPreviewPoseChanged.Invoke(skeleton);
106 }
107
108 public static bool AssociateAllBones(this SpriteCache sprite)
109 {
110 var skinningCache = sprite.skinningCache;
111
112 if (skinningCache.mode == SkinningMode.SpriteSheet)
113 return false;
114
115 var character = skinningCache.character;
116 Debug.Assert(character != null);
117 Debug.Assert(character.skeleton != null);
118
119 var characterPart = sprite.GetCharacterPart();
120
121 Debug.Assert(characterPart != null);
122
123 var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray();
124 characterPart.bones = bones;
125
126 characterPart.sprite.UpdateMesh(bones);
127
128 return true;
129 }
130
131 public static bool AssociatePossibleBones(this SpriteCache sprite)
132 {
133 var skinningCache = sprite.skinningCache;
134
135 if (skinningCache.mode == SkinningMode.SpriteSheet)
136 return false;
137
138 var character = skinningCache.character;
139 Debug.Assert(character != null);
140 Debug.Assert(character.skeleton != null);
141
142 var characterPart = sprite.GetCharacterPart();
143
144 Debug.Assert(characterPart != null);
145
146 var bones = character.skeleton.bones.Where(x => x.isVisible).ToArray();
147 var possibleBones = new List<BoneCache>();
148 // check if any of the bones overlapped
149 BoneCache shortestBoneDistance = null;
150 var minDistances = float.MaxValue;
151 var characterSpriteRect = new Rect(characterPart.position.x , characterPart.position.y, characterPart.sprite.textureRect.width, characterPart.sprite.textureRect.height);
152 foreach (var bone in bones)
153 {
154 var startPoint = bone.position;
155 var endPoint = bone.endPosition;
156 if (IntersectsSegment(characterSpriteRect, startPoint, endPoint))
157 possibleBones.Add(bone);
158 if (possibleBones.Count == 0)
159 {
160 // compare bone start end with rect's 4 line
161 // compare rect point with bone line
162 var points = new Vector2[] { startPoint, endPoint };
163 var rectLinePoints = new []
164 {
165 new Vector2Int(0, 1),
166 new Vector2Int(0, 2),
167 new Vector2Int(1, 3),
168 new Vector2Int(2, 3),
169 };
170 var rectPoints = new []
171 {
172 new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMin),
173 new Vector2(characterSpriteRect.xMin, characterSpriteRect.yMax),
174 new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMin),
175 new Vector2(characterSpriteRect.xMax, characterSpriteRect.yMax)
176 };
177 foreach (var point in points)
178 {
179 foreach (var rectLine in rectLinePoints)
180 {
181 var distance = PointToLineSegmentDistance(point, rectPoints[rectLine.x], rectPoints[rectLine.y]);
182 if (distance < minDistances)
183 {
184 minDistances = distance;
185 shortestBoneDistance = bone;
186 }
187 }
188 }
189
190 foreach (var rectPoint in rectPoints)
191 {
192 var distance = PointToLineSegmentDistance(rectPoint, startPoint, endPoint);
193 if (distance < minDistances)
194 {
195 minDistances = distance;
196 shortestBoneDistance = bone;
197 }
198 }
199 }
200 }
201 // if none overlapped, we use the bone that is closest to us
202 if (possibleBones.Count == 0 && shortestBoneDistance != null)
203 {
204 possibleBones.Add(shortestBoneDistance);
205 }
206 characterPart.bones = possibleBones.ToArray();
207
208 characterPart.sprite.UpdateMesh(possibleBones.ToArray());
209
210 return true;
211 }
212
213 static float PointToLineSegmentDistance(Vector2 p, Vector2 a, Vector2 b)
214 {
215 Vector2 n = b - a;
216 Vector2 pa = a - p;
217
218 float c = Vector2.Dot(n, pa);
219
220 // Closest point is a
221 if (c > 0.0f)
222 return Vector2.Dot(pa, pa);
223
224 Vector2 bp = p - b;
225
226 // Closest point is b
227 if (Vector2.Dot(n, bp) > 0.0f)
228 return Vector2.Dot(bp, bp);
229
230 // Closest point is between a and b
231 Vector2 e = pa - n * (c / Vector2.Dot(n, n));
232 return Vector2.Dot( e, e );
233 }
234
235 static bool IntersectsSegment(Rect rect, Vector2 p1, Vector2 p2)
236 {
237 float minX = Mathf.Min(p1.x, p2.x);
238 float maxX = Mathf.Max(p1.x, p2.x);
239
240 if (maxX > rect.xMax)
241 {
242 maxX = rect.xMax;
243 }
244
245 if (minX < rect.xMin)
246 {
247 minX = rect.xMin;
248 }
249
250 if (minX > maxX)
251 {
252 return false;
253 }
254
255 float minY = Mathf.Min(p1.y, p2.y);
256 float maxY = Mathf.Max(p1.y, p2.y);
257
258 float dx = p2.x - p1.x;
259
260 if (Mathf.Abs(dx) > float.Epsilon)
261 {
262 float a = (p2.y - p1.y) / dx;
263 float b = p1.y - a * p1.x;
264 minY = a * minX + b;
265 maxY = a * maxX + b;
266 }
267
268 if (minY > maxY)
269 {
270 float tmp = maxY;
271 maxY = minY;
272 minY = tmp;
273 }
274
275 if (maxY > rect.yMax)
276 {
277 maxY = rect.yMax;
278 }
279
280 if (minY < rect.yMin)
281 {
282 minY = rect.yMin;
283 }
284
285 if (minY > maxY)
286 {
287 return false;
288 }
289
290 return true;
291 }
292
293 public static void DeassociateUnusedBones(this SpriteCache sprite)
294 {
295 var skinningCache = sprite.skinningCache;
296
297 Debug.Assert(skinningCache.mode == SkinningMode.Character);
298
299 var characterPart = sprite.GetCharacterPart();
300
301 Debug.Assert(characterPart != null);
302
303 characterPart.DissociateUnusedBones();
304 }
305
306 public static void DeassociateAllBones(this SpriteCache sprite)
307 {
308 var skinningCache = sprite.skinningCache;
309
310 if (skinningCache.mode == SkinningMode.SpriteSheet)
311 return;
312
313 var part = sprite.GetCharacterPart();
314 if (part.bones.Length == 0)
315 return;
316
317 Debug.Assert(part.sprite != null);
318
319 part.bones = new BoneCache[0];
320 part.sprite.UpdateMesh(part.bones);
321
322 skinningCache.events.characterPartChanged.Invoke(part);
323 }
324 }
325}