| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine; |
| using UnityEngine.Timeline; |
| using UnityEngine.Playables; |
|
|
| namespace UnityEditor.Timeline |
| { |
| |
| |
| |
| public static class TrackExtensions |
| { |
| static readonly double kMinOverlapTime = TimeUtility.kTimeEpsilon * 1000; |
|
|
| |
| |
| |
| |
| |
| public static bool IsCollapsed(this TrackAsset track) |
| { |
| return TimelineWindowViewPrefs.IsTrackCollapsed(track); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public static void SetCollapsed(this TrackAsset track, bool collapsed) |
| { |
| TimelineWindowViewPrefs.SetTrackCollapsed(track, collapsed); |
| } |
|
|
| |
| |
| |
| |
| |
| public static bool IsVisibleInHierarchy(this TrackAsset track) |
| { |
| var t = track; |
| while ((t = t.parent as TrackAsset) != null) |
| { |
| if (t.IsCollapsed()) |
| return false; |
| } |
|
|
| return true; |
| } |
|
|
| internal static AnimationClip GetOrCreateClip(this AnimationTrack track) |
| { |
| if (track.infiniteClip == null && !track.inClipMode) |
| track.CreateInfiniteClip(AnimationTrackRecorder.GetUniqueRecordedClipName(track, AnimationTrackRecorder.kRecordClipDefaultName)); |
|
|
| return track.infiniteClip; |
| } |
|
|
| internal 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, 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; |
| } |
|
|
| internal 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; |
| } |
|
|
| Array.Sort(clips, (c1, c2) => |
| Math.Abs(c1.start - c2.start) < TimeUtility.kTimeEpsilon ? c1.duration.CompareTo(c2.duration) : c1.start.CompareTo(c2.start)); |
|
|
| for (var i = 0; i < clips.Length; i++) |
| { |
| var clip = clips[i]; |
| if (!clip.SupportsBlending()) |
| continue; |
| var blendIn = clip; |
| TimelineClip blendOut = null; |
|
|
| var blendOutCandidate = clips[Math.Max(i - 1, 0)]; |
| if (Overlaps(blendOutCandidate, blendIn)) |
| blendOut = blendOutCandidate; |
|
|
| if (blendOut != null) |
| { |
| UpdateClipIntersection(blendOut, blendIn); |
| } |
| } |
| } |
|
|
| static void UpdateClipIntersection(TimelineClip blendOutClip, TimelineClip blendInClip) |
| { |
| if (!blendOutClip.SupportsBlending() || !blendInClip.SupportsBlending()) |
| return; |
|
|
| if (blendInClip.start - blendOutClip.start < blendOutClip.duration - blendInClip.duration) |
| 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; |
| blendOutClip.mixOutCurve = null; |
| } |
| } |
|
|
| 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); |
|
|
| |
| var customEditor = CustomTimelineEditorCache.GetTrackEditor(newSub); |
| customEditor.OnCreate_Safe(newSub, sub); |
|
|
| |
| TimelineCreateUtilities.SaveAssetIntoObject(newSub, assetOwner); |
| TimelineUndo.RegisterCreatedObjectUndo(newSub, L10n.Tr("Duplicate")); |
| } |
| } |
|
|
| internal static TrackAsset Duplicate(this TrackAsset track, IExposedPropertyTable sourceTable, IExposedPropertyTable destTable, |
| TimelineAsset destinationTimeline = null) |
| { |
| if (track == null) |
| return null; |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| PlayableAsset finalParent = destinationTimeline != null ? destinationTimeline : track.parent; |
|
|
| |
| |
| var finalTrackParent = finalParent as TrackAsset; |
| var finalTimelineAsset = finalParent as TimelineAsset; |
| var otherTracks = (finalTimelineAsset != null) ? finalTimelineAsset.trackObjects : finalTrackParent.subTracksObjects; |
|
|
| |
| |
| 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, L10n.Tr("Duplicate")); |
| UndoExtensions.RegisterPlayableAsset(finalParent, L10n.Tr("Duplicate")); |
|
|
| if (destinationTimeline != null) |
| destinationTimeline.AddTrackInternal(newTrack); |
| else if (timelineParent != null) |
| ReparentTracks(new List<TrackAsset> { newTrack }, timelineParent, timelineParent.GetRootTracks().Last(), false); |
| else |
| trackParent.AddChild(newTrack); |
|
|
| |
| if (destinationTimeline == null || destinationTimeline == TimelineEditor.inspectedAsset) |
| { |
| var customEditor = CustomTimelineEditorCache.GetTrackEditor(newTrack); |
| customEditor.OnCreate_Safe(newTrack, track); |
| } |
|
|
| return newTrack; |
| } |
|
|
| |
| |
| |
| 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; |
|
|
| |
| if (targetParentTrack != null && tracksToMove.Any(x => !TimelineCreateUtilities.ValidateParentTrack(targetParentTrack, x.GetType()))) |
| return false; |
|
|
| |
| 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(); |
| |
| foreach (var p in parents) |
| { |
| UndoExtensions.RegisterPlayableAsset(p, "Reparent"); |
| } |
| UndoExtensions.RegisterTracks(validTracks, "Reparent"); |
| UndoExtensions.RegisterPlayableAsset(targetParent, "Reparent"); |
|
|
| |
| 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) |
| { |
| |
| |
| 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(); |
| |
| 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; |
| |
| 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 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 AnimationTrack track) |
| { |
| return track != null && !track.inClipMode && track.infiniteClip != null; |
| } |
|
|
| |
| 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"); |
|
|
| |
| object trackAsObject = track; |
| if (trackAsObject == null || track as TrackAsset != null) |
| throw new ArgumentException("track is not in a broken state"); |
|
|
| |
| if (parentTrack != null) |
| { |
| int index = parentTrack.subTracksObjects.FindIndex(t => t.GetInstanceID() == track.GetInstanceID()); |
| if (index >= 0) |
| { |
| UndoExtensions.RegisterTrack(parentTrack, L10n.Tr("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) |
| { |
| UndoExtensions.RegisterPlayableAsset(parentTimeline, L10n.Tr("Remove Track")); |
| parentTimeline.trackObjects.RemoveAt(index); |
| parentTimeline.Invalidate(); |
| Undo.DestroyObjectImmediate(track); |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| 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]; |
|
|
| |
| 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; |
| } |
|
|
| internal 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()); |
| } |
|
|
| |
| 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); |
| } |
| } |
|
|
| internal 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; |
| } |
|
|
| internal static void UnarmForRecord(this TrackAsset track) |
| { |
| TimelineWindow.instance.state.UnarmForRecord(track); |
| } |
|
|
| internal static void SetShowTrackMarkers(this TrackAsset track, bool showMarkers) |
| { |
| var currentValue = track.GetShowMarkers(); |
| if (currentValue != showMarkers) |
| { |
| TimelineUndo.PushUndo(TimelineWindow.instance.state.editSequence.viewModel, L10n.Tr("Toggle Show Markers")); |
| track.SetShowMarkers(showMarkers); |
| if (!showMarkers) |
| { |
| foreach (var marker in track.GetMarkers()) |
| { |
| SelectionManager.Remove(marker); |
| } |
| } |
| } |
| } |
|
|
| internal static IEnumerable<TrackAsset> RemoveTimelineMarkerTrackFromList(this IEnumerable<TrackAsset> tracks, TimelineAsset asset) |
| { |
| return tracks.Where(t => t != asset.markerTrack); |
| } |
|
|
| internal static bool ContainsTimelineMarkerTrack(this IEnumerable<TrackAsset> tracks, TimelineAsset asset) |
| { |
| return tracks.Contains(asset.markerTrack); |
| } |
| } |
| } |
|
|