TrackExtensions.cs
18.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
namespace UnityEditor.Timeline
{
// Editor-only extension methods on track assets
static class TrackExtensions
{
public static readonly double kMinOverlapTime = TimeUtility.kTimeEpsilon * 1000;
public static AnimationClip GetOrCreateClip(this AnimationTrack track)
{
if (track.infiniteClip == null && !track.inClipMode)
track.CreateInfiniteClip(AnimationTrackRecorder.GetUniqueRecordedClipName(track, AnimationTrackRecorder.kRecordClipDefaultName));
return track.infiniteClip;
}
public static TimelineClip CreateClip(this TrackAsset track, double time)
{
var attr = track.GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
if (attr.Length == 0)
return null;
if (TimelineWindow.instance.state == null)
return null;
if (attr.Length == 1)
{
var clipClass = (TrackClipTypeAttribute)attr[0];
var clip = TimelineHelpers.CreateClipOnTrack(clipClass.inspectedType, track, TimelineWindow.instance.state);
clip.start = time;
return clip;
}
return null;
}
static bool Overlaps(TimelineClip blendOut, TimelineClip blendIn)
{
if (blendIn == blendOut)
return false;
if (Math.Abs(blendIn.start - blendOut.start) < TimeUtility.kTimeEpsilon)
{
return blendIn.duration >= blendOut.duration;
}
return blendIn.start >= blendOut.start && blendIn.start < blendOut.end;
}
public static void ComputeBlendsFromOverlaps(this TrackAsset asset)
{
ComputeBlendsFromOverlaps(asset.clips);
}
internal static void ComputeBlendsFromOverlaps(TimelineClip[] clips)
{
foreach (var clip in clips)
{
clip.blendInDuration = -1;
clip.blendOutDuration = -1;
}
foreach (var clip in clips)
{
var blendIn = clip;
var blendOut = clips
.Where(c => Overlaps(c, blendIn))
.OrderBy(c => c.start)
.FirstOrDefault();
if (blendOut != null)
{
UpdateClipIntersection(blendOut, blendIn);
}
}
}
internal static void UpdateClipIntersection(TimelineClip blendOutClip, TimelineClip blendInClip)
{
if (!blendOutClip.SupportsBlending() || !blendInClip.SupportsBlending())
return;
if (blendInClip.end < blendOutClip.end)
return;
double duration = Math.Max(0, blendOutClip.start + blendOutClip.duration - blendInClip.start);
duration = duration <= kMinOverlapTime ? 0 : duration;
blendOutClip.blendOutDuration = duration;
blendInClip.blendInDuration = duration;
var blendInMode = blendInClip.blendInCurveMode;
var blendOutMode = blendOutClip.blendOutCurveMode;
if (blendInMode == TimelineClip.BlendCurveMode.Manual && blendOutMode == TimelineClip.BlendCurveMode.Auto)
{
blendOutClip.mixOutCurve = CurveEditUtility.CreateMatchingCurve(blendInClip.mixInCurve);
}
else if (blendInMode == TimelineClip.BlendCurveMode.Auto && blendOutMode == TimelineClip.BlendCurveMode.Manual)
{
blendInClip.mixInCurve = CurveEditUtility.CreateMatchingCurve(blendOutClip.mixOutCurve);
}
else if (blendInMode == TimelineClip.BlendCurveMode.Auto && blendOutMode == TimelineClip.BlendCurveMode.Auto)
{
blendInClip.mixInCurve = null; // resets to default curves
blendOutClip.mixOutCurve = null;
}
}
internal static void RecursiveSubtrackClone(TrackAsset source, TrackAsset duplicate, IExposedPropertyTable sourceTable, IExposedPropertyTable destTable, PlayableAsset assetOwner)
{
var subtracks = source.GetChildTracks();
foreach (var sub in subtracks)
{
var newSub = TimelineHelpers.Clone(duplicate, sub, sourceTable, destTable, assetOwner);
duplicate.AddChild(newSub);
RecursiveSubtrackClone(sub, newSub, sourceTable, destTable, assetOwner);
// Call the custom editor on Create
var customEditor = CustomTimelineEditorCache.GetTrackEditor(newSub);
try
{
customEditor.OnCreate(newSub, sub);
}
catch (Exception e)
{
Debug.LogException(e);
}
// registration has to happen AFTER recursion
TimelineCreateUtilities.SaveAssetIntoObject(newSub, assetOwner);
TimelineUndo.RegisterCreatedObjectUndo(newSub, "Duplicate");
}
}
internal static TrackAsset Duplicate(this TrackAsset track, IExposedPropertyTable sourceTable, IExposedPropertyTable destTable,
TimelineAsset destinationTimeline = null)
{
if (track == null)
return null;
// if the destination is us, clear to avoid bad parenting (case 919421)
if (destinationTimeline == track.timelineAsset)
destinationTimeline = null;
var timelineParent = track.parent as TimelineAsset;
var trackParent = track.parent as TrackAsset;
if (timelineParent == null && trackParent == null)
{
Debug.LogWarning("Cannot duplicate track because it is not parented to known type");
return null;
}
// Determine who the final parent is. If we are pasting into another track, it's always the timeline.
// Otherwise it's the original parent
PlayableAsset finalParent = destinationTimeline != null ? destinationTimeline : track.parent;
// grab the list of tracks to generate a name from (923360) to get the list of names
// no need to do this part recursively
var finalTrackParent = finalParent as TrackAsset;
var finalTimelineAsset = finalParent as TimelineAsset;
var otherTracks = (finalTimelineAsset != null) ? finalTimelineAsset.trackObjects : finalTrackParent.subTracksObjects;
// Important to create the new objects before pushing the original undo, or redo breaks the
// sequence
var newTrack = TimelineHelpers.Clone(finalParent, track, sourceTable, destTable, finalParent);
newTrack.name = TimelineCreateUtilities.GenerateUniqueActorName(otherTracks, newTrack.name);
RecursiveSubtrackClone(track, newTrack, sourceTable, destTable, finalParent);
TimelineCreateUtilities.SaveAssetIntoObject(newTrack, finalParent);
TimelineUndo.RegisterCreatedObjectUndo(newTrack, "Duplicate");
TimelineUndo.PushUndo(finalParent, "Duplicate");
if (destinationTimeline != null) // other timeline
destinationTimeline.AddTrackInternal(newTrack);
else if (timelineParent != null) // this timeline, no parent
ReparentTracks(new List<TrackAsset> { newTrack }, timelineParent, timelineParent.GetRootTracks().Last(), false);
else // this timeline, with parent
trackParent.AddChild(newTrack);
// Call the custom editor. this check prevents the call when copying to the clipboard
if (destinationTimeline == null || destinationTimeline == TimelineEditor.inspectedAsset)
{
var customEditor = CustomTimelineEditorCache.GetTrackEditor(newTrack);
try
{
customEditor.OnCreate(newTrack, track);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
return newTrack;
}
// Reparents a list of tracks to a new parent
// the new parent cannot be null (has to be track asset or sequence)
// the insertAfter can be null (will not reorder)
internal static bool ReparentTracks(List<TrackAsset> tracksToMove, PlayableAsset targetParent,
TrackAsset insertMarker = null, bool insertBefore = false)
{
var targetParentTrack = targetParent as TrackAsset;
var targetSequenceTrack = targetParent as TimelineAsset;
if (tracksToMove == null || tracksToMove.Count == 0 || (targetParentTrack == null && targetSequenceTrack == null))
return false;
// invalid parent type on a track
if (targetParentTrack != null && tracksToMove.Any(x => !TimelineCreateUtilities.ValidateParentTrack(targetParentTrack, x.GetType())))
return false;
// no valid tracks means this is simply a rearrangement
var validTracks = tracksToMove.Where(x => x.parent != targetParent).ToList();
if (insertMarker == null && !validTracks.Any())
return false;
var parents = validTracks.Select(x => x.parent).Where(x => x != null).Distinct().ToList();
// push the current state of the tracks that will change
foreach (var p in parents)
{
TimelineUndo.PushUndo(p, "Reparent");
}
foreach (var t in validTracks)
{
TimelineUndo.PushUndo(t, "Reparent");
}
TimelineUndo.PushUndo(targetParent, "Reparent");
// need to reparent tracks first, before moving them.
foreach (var t in validTracks)
{
if (t.parent != targetParent)
{
TrackAsset toMoveParent = t.parent as TrackAsset;
TimelineAsset toMoveTimeline = t.parent as TimelineAsset;
if (toMoveTimeline != null)
{
toMoveTimeline.RemoveTrack(t);
}
else if (toMoveParent != null)
{
toMoveParent.RemoveSubTrack(t);
}
if (targetParentTrack != null)
{
targetParentTrack.AddChild(t);
targetParentTrack.SetCollapsed(false);
}
else
{
targetSequenceTrack.AddTrackInternal(t);
}
}
}
if (insertMarker != null)
{
// re-ordering track. This is using internal APIs, so invalidation of the tracks must be done manually to avoid
// cache mismatches
var children = targetParentTrack != null ? targetParentTrack.subTracksObjects : targetSequenceTrack.trackObjects;
TimelineUtility.ReorderTracks(children, tracksToMove, insertMarker, insertBefore);
if (targetParentTrack != null)
targetParentTrack.Invalidate();
if (insertMarker.timelineAsset != null)
insertMarker.timelineAsset.Invalidate();
}
return true;
}
internal static IEnumerable<TrackAsset> FilterTracks(IEnumerable<TrackAsset> tracks)
{
var nTracks = tracks.Count();
// Duplicate is recursive. If should not have parent and child in the list
var hash = new HashSet<TrackAsset>(tracks);
var take = new Dictionary<TrackAsset, bool>(nTracks);
foreach (var track in tracks)
{
var parent = track.parent as TrackAsset;
var foundParent = false;
// go up the hierarchy
while (parent != null && !foundParent)
{
if (hash.Contains(parent))
{
foundParent = true;
}
parent = parent.parent as TrackAsset;
}
take[track] = !foundParent;
}
foreach (var track in tracks)
{
if (take[track])
yield return track;
}
}
internal static bool IsVisibleRecursive(this TrackAsset track)
{
var t = track;
while ((t = t.parent as TrackAsset) != null)
{
if (t.GetCollapsed())
return false;
}
return true;
}
internal static bool GetCollapsed(this TrackAsset track)
{
return TimelineWindowViewPrefs.IsTrackCollapsed(track);
}
internal static void SetCollapsed(this TrackAsset track, bool collapsed)
{
TimelineWindowViewPrefs.SetTrackCollapsed(track, collapsed);
}
internal static bool GetShowMarkers(this TrackAsset track)
{
return TimelineWindowViewPrefs.IsShowMarkers(track);
}
internal static void SetShowMarkers(this TrackAsset track, bool collapsed)
{
TimelineWindowViewPrefs.SetTrackShowMarkers(track, collapsed);
}
internal static bool GetShowInlineCurves(this TrackAsset track)
{
return TimelineWindowViewPrefs.GetShowInlineCurves(track);
}
internal static void SetShowInlineCurves(this TrackAsset track, bool inlineOn)
{
TimelineWindowViewPrefs.SetShowInlineCurves(track, inlineOn);
}
internal static bool ShouldShowInfiniteClipEditor(this TrackAsset track)
{
var animationTrack = track as AnimationTrack;
if (animationTrack != null)
return animationTrack.ShouldShowInfiniteClipEditor();
return track.HasAnyAnimatableParameters();
}
internal static bool ShouldShowInfiniteClipEditor(this AnimationTrack track)
{
return track != null && !track.inClipMode && track.infiniteClip != null;
}
// Special method to remove a track that is in a broken state. i.e. the script won't load
internal static bool RemoveBrokenTrack(PlayableAsset parent, ScriptableObject track)
{
var parentTrack = parent as TrackAsset;
var parentTimeline = parent as TimelineAsset;
if (parentTrack == null && parentTimeline == null)
throw new ArgumentException("parent is not a valid parent type", "parent");
// this object must be a Unity null, but not actually null;
object trackAsObject = track;
if (trackAsObject == null || track != null) // yes, this is correct
throw new ArgumentException("track is not in a broken state");
// this belongs to a parent track
if (parentTrack != null)
{
int index = parentTrack.subTracksObjects.FindIndex(t => t.GetInstanceID() == track.GetInstanceID());
if (index >= 0)
{
TimelineUndo.PushUndo(parentTrack, "Remove Track");
parentTrack.subTracksObjects.RemoveAt(index);
parentTrack.Invalidate();
Undo.DestroyObjectImmediate(track);
return true;
}
}
else if (parentTimeline != null)
{
int index = parentTimeline.trackObjects.FindIndex(t => t.GetInstanceID() == track.GetInstanceID());
if (index >= 0)
{
TimelineUndo.PushUndo(parentTimeline, "Remove Track");
parentTimeline.trackObjects.RemoveAt(index);
parentTimeline.Invalidate();
Undo.DestroyObjectImmediate(track);
return true;
}
}
return false;
}
// Find the gap at the given time
// return true if there is a gap, false if there is an intersection
// endGap will be Infinity if the gap has no end
internal static bool GetGapAtTime(this TrackAsset track, double time, out double startGap, out double endGap)
{
startGap = 0;
endGap = Double.PositiveInfinity;
if (track == null || !track.GetClips().Any())
{
return false;
}
var discreteTime = new DiscreteTime(time);
track.SortClips();
var sortedByStartTime = track.clips;
for (int i = 0; i < sortedByStartTime.Length; i++)
{
var clip = sortedByStartTime[i];
// intersection
if (discreteTime >= new DiscreteTime(clip.start) && discreteTime < new DiscreteTime(clip.end))
{
endGap = time;
startGap = time;
return false;
}
if (clip.end < time)
{
startGap = clip.end;
}
if (clip.start > time)
{
endGap = clip.start;
break;
}
}
if (endGap - startGap < TimelineClip.kMinDuration)
{
startGap = time;
endGap = time;
return false;
}
return true;
}
public static bool IsCompatibleWithClip(this TrackAsset track, TimelineClip clip)
{
if (track == null || clip == null || clip.asset == null)
return false;
return TypeUtility.GetPlayableAssetsHandledByTrack(track.GetType()).Contains(clip.asset.GetType());
}
// Get a flattened list of all child tracks
public static void GetFlattenedChildTracks(this TrackAsset asset, List<TrackAsset> list)
{
if (asset == null || list == null)
return;
foreach (var track in asset.GetChildTracks())
{
list.Add(track);
GetFlattenedChildTracks(track, list);
}
}
public static IEnumerable<TrackAsset> GetFlattenedChildTracks(this TrackAsset asset)
{
if (asset == null || !asset.GetChildTracks().Any())
return Enumerable.Empty<TrackAsset>();
var flattenedChildTracks = new List<TrackAsset>();
GetFlattenedChildTracks(asset, flattenedChildTracks);
return flattenedChildTracks;
}
}
}