A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityEditor;
6using UnityEngineInternal;
7using UnityEngine.Timeline;
8using UnityEngine.Playables;
9using Object = UnityEngine.Object;
10
11namespace UnityEditor.Timeline
12{
13 class TimelineAnimationUtilities
14 {
15 public enum OffsetEditMode
16 {
17 None = -1,
18 Translation = 0,
19 Rotation = 1
20 }
21
22 public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator)
23 {
24 if (director == null || animator == null)
25 return false;
26
27 return true;
28 }
29
30 public static TimelineClip GetPreviousClip(TimelineClip clip)
31 {
32 TimelineClip previousClip = null;
33 foreach (var c in clip.GetParentTrack().clips)
34 {
35 if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
36 previousClip = c;
37 }
38 return previousClip;
39 }
40
41 public static TimelineClip GetNextClip(TimelineClip clip)
42 {
43 return clip.GetParentTrack().clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault();
44 }
45
46 public struct RigidTransform
47 {
48 public Vector3 position;
49 public Quaternion rotation;
50
51 public static RigidTransform Compose(Vector3 pos, Quaternion rot)
52 {
53 RigidTransform ret;
54 ret.position = pos;
55 ret.rotation = rot;
56 return ret;
57 }
58
59 public static RigidTransform Mul(RigidTransform a, RigidTransform b)
60 {
61 RigidTransform ret;
62 ret.rotation = a.rotation * b.rotation;
63 ret.position = a.position + a.rotation * b.position;
64 return ret;
65 }
66
67 public static RigidTransform Inverse(RigidTransform a)
68 {
69 RigidTransform ret;
70 ret.rotation = Quaternion.Inverse(a.rotation);
71 ret.position = ret.rotation * (-a.position);
72 return ret;
73 }
74
75 public static RigidTransform identity
76 {
77 get { return Compose(Vector3.zero, Quaternion.identity); }
78 }
79 }
80
81
82 private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track)
83 {
84 Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one);
85
86 // in scene off mode, the track offsets are set to the preview position which is stored in the track
87 if (track.trackOffset == TrackOffset.ApplySceneOffsets)
88 {
89 trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one);
90 }
91
92 // put the parent transform on to the track matrix
93 if (transform.parent != null)
94 {
95 trackMatrix = transform.parent.localToWorldMatrix * trackMatrix;
96 }
97
98 return trackMatrix;
99 }
100
101 // Given a world space position and rotation, updates the clip offsets to match that
102 public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation)
103 {
104 Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
105 Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one);
106 Matrix4x4 trackMatrix = GetTrackMatrix(transform, track);
107
108
109 // Use the transform to find the proper goal matrix with scale taken into account
110 var oldPos = transform.position;
111 var oldRot = transform.rotation;
112 transform.position = globalPosition;
113 transform.rotation = globalRotation;
114 Matrix4x4 goal = transform.localToWorldMatrix;
115 transform.position = oldPos;
116 transform.rotation = oldRot;
117
118 // compute the new clip matrix.
119 Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix;
120 return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip));
121 }
122
123 public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform)
124 {
125 Vector3 position = track.position;
126 Quaternion rotation = track.rotation;
127 if (transform != null && transform.parent != null)
128 {
129 position = transform.parent.TransformPoint(position);
130 rotation = transform.parent.rotation * rotation;
131 MathUtils.QuaternionNormalize(ref rotation);
132 }
133
134 return RigidTransform.Compose(position, rotation);
135 }
136
137 public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets)
138 {
139 if (transform != null && transform.parent != null)
140 {
141 offsets.position = transform.parent.InverseTransformPoint(offsets.position);
142 offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation;
143 MathUtils.QuaternionNormalize(ref offsets.rotation);
144 }
145
146 track.position = offsets.position;
147 track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY);
148 track.UpdateClipOffsets();
149 }
150
151 static MatchTargetFields GetMatchFields(TimelineClip clip)
152 {
153 var track = clip.GetParentTrack() as AnimationTrack;
154 if (track == null)
155 return MatchTargetFieldConstants.None;
156
157 var asset = clip.asset as AnimationPlayableAsset;
158 var fields = track.matchTargetFields;
159 if (asset != null && !asset.useTrackMatchFields)
160 fields = asset.matchTargetFields;
161 return fields;
162 }
163
164 static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields)
165 {
166 Vector3 position = asset.position;
167
168 position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x;
169 position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y;
170 position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z;
171
172 asset.position = position;
173
174 // check first to avoid unnecessary conversion errors
175 if (fields.HasAny(MatchTargetFieldConstants.Rotation))
176 {
177 Vector3 eulers = asset.eulerAngles;
178 Vector3 resultEulers = result.rotation.eulerAngles;
179
180 eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x;
181 eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y;
182 eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z;
183
184 asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY);
185 }
186 }
187
188 public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
189 {
190 const double timeEpsilon = 0.00001;
191 MatchTargetFields matchFields = GetMatchFields(currentClip);
192 if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
193 return;
194
195 double cachedTime = director.time;
196
197 // finds previous clip
198 TimelineClip previousClip = GetPreviousClip(currentClip);
199 if (previousClip == null || currentClip == previousClip)
200 return;
201
202 // make sure the transform is properly updated before modifying the graph
203 director.Evaluate();
204
205 var parentTrack = currentClip.GetParentTrack() as AnimationTrack;
206
207 var blendIn = currentClip.blendInDuration;
208 currentClip.blendInDuration = 0;
209 var blendOut = previousClip.blendOutDuration;
210 previousClip.blendOutDuration = 0;
211
212 //evaluate previous without current
213 parentTrack.RemoveClip(currentClip);
214 director.RebuildGraph();
215 double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start;
216 director.time = previousEndTime - timeEpsilon;
217 director.Evaluate(); // add port to evaluate only track
218
219 var targetPosition = matchPoint.position;
220 var targetRotation = matchPoint.rotation;
221
222 // evaluate current without previous
223 parentTrack.AddClip(currentClip);
224 parentTrack.RemoveClip(previousClip);
225 director.RebuildGraph();
226 director.time = currentClip.start + timeEpsilon;
227 director.Evaluate();
228
229 //////////////////////////////////////////////////////////////////////
230 //compute offsets
231
232 var animationPlayable = currentClip.asset as AnimationPlayableAsset;
233 var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
234 WriteMatchFields(animationPlayable, match, matchFields);
235
236 //////////////////////////////////////////////////////////////////////
237
238 currentClip.blendInDuration = blendIn;
239 previousClip.blendOutDuration = blendOut;
240
241 parentTrack.AddClip(previousClip);
242 director.RebuildGraph();
243 director.time = cachedTime;
244 director.Evaluate();
245 }
246
247 public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
248 {
249 const double timeEpsilon = 0.00001;
250 MatchTargetFields matchFields = GetMatchFields(currentClip);
251 if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
252 return;
253
254 double cachedTime = director.time;
255
256 // finds next clip
257 TimelineClip nextClip = GetNextClip(currentClip);
258 if (nextClip == null || currentClip == nextClip)
259 return;
260
261 // make sure the transform is properly updated before modifying the graph
262 director.Evaluate();
263
264 var parentTrack = currentClip.GetParentTrack() as AnimationTrack;
265
266 var blendOut = currentClip.blendOutDuration;
267 var blendIn = nextClip.blendInDuration;
268 currentClip.blendOutDuration = 0;
269 nextClip.blendInDuration = 0;
270
271 //evaluate previous without current
272 parentTrack.RemoveClip(currentClip);
273 director.RebuildGraph();
274 director.time = nextClip.start + timeEpsilon;
275 director.Evaluate(); // add port to evaluate only track
276
277 var targetPosition = matchPoint.position;
278 var targetRotation = matchPoint.rotation;
279
280 // evaluate current without next
281 parentTrack.AddClip(currentClip);
282 parentTrack.RemoveClip(nextClip);
283 director.RebuildGraph();
284 director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon);
285 director.Evaluate();
286
287 //////////////////////////////////////////////////////////////////////
288 //compute offsets
289
290 var animationPlayable = currentClip.asset as AnimationPlayableAsset;
291 var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
292 WriteMatchFields(animationPlayable, match, matchFields);
293
294 //////////////////////////////////////////////////////////////////////
295
296 currentClip.blendOutDuration = blendOut;
297 nextClip.blendInDuration = blendIn;
298
299 parentTrack.AddClip(nextClip);
300 director.RebuildGraph();
301 director.time = cachedTime;
302 director.Evaluate();
303 }
304
305 public static TimelineWindowTimeControl CreateTimeController(TimelineClip clip)
306 {
307 var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
308 var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
309 timeController.Init(animationWindow.state, clip);
310 return timeController;
311 }
312
313 public static TimelineWindowTimeControl CreateTimeController(TimelineWindowTimeControl.ClipData clipData)
314 {
315 var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
316 var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
317 timeController.Init(animationWindow.state, clipData);
318 return timeController;
319 }
320
321 public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject)
322 {
323 var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
324 animationWindow.EditSequencerClip(animationClip, sourceObject, timeController);
325 }
326
327 public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks)
328 {
329 var clips = new List<AnimationClip>();
330 foreach (var track in tracks)
331 {
332 var animationTrack = track as AnimationTrack;
333 if (animationTrack != null && animationTrack.infiniteClip != null)
334 clips.Add(animationTrack.infiniteClip);
335
336 GetAnimationClips(track.GetClips(), clips);
337 }
338 UnlinkAnimationWindowFromAnimationClips(clips);
339 }
340
341 public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips)
342 {
343 var clips = new List<AnimationClip>();
344 GetAnimationClips(timelineClips, clips);
345 UnlinkAnimationWindowFromAnimationClips(clips);
346 }
347
348 public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips)
349 {
350 if (clips.Count == 0)
351 return;
352
353 UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
354 foreach (var animWindow in windows.OfType<AnimationWindow>())
355 {
356 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip))
357 animWindow.UnlinkSequencer();
358 }
359 }
360
361 public static void UnlinkAnimationWindow()
362 {
363 UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
364 foreach (var animWindow in windows.OfType<AnimationWindow>())
365 {
366 if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer)
367 animWindow.UnlinkSequencer();
368 }
369 }
370
371 private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips)
372 {
373 foreach (var timelineClip in timelineClips)
374 {
375 if (timelineClip.curves != null)
376 clips.Add(timelineClip.curves);
377 AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset;
378 if (apa != null && apa.clip != null)
379 clips.Add(apa.clip);
380 }
381 }
382
383 public static int GetAnimationWindowCurrentFrame()
384 {
385 var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
386 if (animationWindow)
387 return animationWindow.state.currentFrame;
388 return -1;
389 }
390
391 public static void SetAnimationWindowCurrentFrame(int frame)
392 {
393 var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
394 if (animationWindow)
395 animationWindow.state.currentFrame = frame;
396 }
397
398 public static void ConstrainCurveToBooleanValues(AnimationCurve curve)
399 {
400 // Clamp the values first
401 var keys = curve.keys;
402 for (var i = 0; i < keys.Length; i++)
403 {
404 var key = keys[i];
405 key.value = key.value < 0.5f ? 0.0f : 1.0f;
406 keys[i] = key;
407 }
408 curve.keys = keys;
409
410 // Update the tangents once all the values are clamped
411 for (var i = 0; i < curve.length; i++)
412 {
413 AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
414 AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
415 }
416 }
417
418 public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue)
419 {
420 var keys = curve.keys;
421 for (var i = 0; i < keys.Length; i++)
422 {
423 var key = keys[i];
424 key.value = Mathf.Clamp(key.value, minValue, maxValue);
425 keys[i] = key;
426 }
427 curve.keys = keys;
428 }
429
430 public static bool IsAnimationClip(TimelineClip clip)
431 {
432 return clip != null && (clip.asset as AnimationPlayableAsset) != null;
433 }
434 }
435}