398 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			398 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using System.Linq; | ||
|  | using UnityEngine.Timeline; | ||
|  | using UnityEngine.Playables; | ||
|  | 
 | ||
|  | namespace UnityEditor.Timeline | ||
|  | { | ||
|  |     static class ClipModifier | ||
|  |     { | ||
|  |         public static bool Delete(TimelineAsset timeline, TimelineClip clip) | ||
|  |         { | ||
|  |             return timeline.DeleteClip(clip); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool Tile(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             if (clips.Count() < 2) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             var clipsByTracks = clips.GroupBy(x => x.GetParentTrack()) | ||
|  |                 .Select(track => new { track.Key, Items = track.OrderBy(c => c.start) }); | ||
|  | 
 | ||
|  |             foreach (var track in clipsByTracks) | ||
|  |             { | ||
|  |                 UndoExtensions.RegisterTrack(track.Key, L10n.Tr("Tile")); | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var track in clipsByTracks) | ||
|  |             { | ||
|  |                 double newStart = track.Items.First().start; | ||
|  |                 foreach (var c in track.Items) | ||
|  |                 { | ||
|  |                     c.start = newStart; | ||
|  |                     newStart += c.duration; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool TrimStart(IEnumerable<TimelineClip> clips, double trimTime) | ||
|  |         { | ||
|  |             var result = false; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |                 result |= TrimStart(clip, trimTime); | ||
|  | 
 | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool TrimStart(TimelineClip clip, double trimTime) | ||
|  |         { | ||
|  |             if (clip.asset == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (clip.start > trimTime) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (clip.end < trimTime) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Start")); | ||
|  | 
 | ||
|  |             // Note: We are NOT using edit modes in this case because we want the same result | ||
|  |             // regardless of the selected EditMode: split at cursor and delete left part | ||
|  |             SetStart(clip, trimTime, false); | ||
|  |             clip.ConformEaseValues(); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool TrimEnd(IEnumerable<TimelineClip> clips, double trimTime) | ||
|  |         { | ||
|  |             var result = false; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |                 result |= TrimEnd(clip, trimTime); | ||
|  | 
 | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool TrimEnd(TimelineClip clip, double trimTime) | ||
|  |         { | ||
|  |             if (clip.asset == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (clip.start > trimTime) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             if (clip.end < trimTime) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip End")); | ||
|  |             TrimClipWithEditMode(clip, TrimEdge.End, trimTime); | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool MatchDuration(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             double referenceDuration = clips.First().duration; | ||
|  |             UndoExtensions.RegisterClips(clips, L10n.Tr("Match Clip Duration")); | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 var newEnd = clip.start + referenceDuration; | ||
|  |                 TrimClipWithEditMode(clip, TrimEdge.End, newEnd); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool Split(IEnumerable<TimelineClip> clips, double splitTime, PlayableDirector director) | ||
|  |         { | ||
|  |             var result = false; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 if (clip.start >= splitTime) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 if (clip.end <= splitTime) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 UndoExtensions.RegisterClip(clip, L10n.Tr("Split Clip")); | ||
|  | 
 | ||
|  |                 TimelineClip newClip = TimelineHelpers.Clone(clip, director, director, clip.start); | ||
|  | 
 | ||
|  |                 clip.easeInDuration = 0; | ||
|  |                 newClip.easeOutDuration = 0; | ||
|  | 
 | ||
|  |                 SetStart(clip, splitTime, false); | ||
|  |                 SetEnd(newClip, splitTime, false); | ||
|  | 
 | ||
|  |                 // Sort produced by cloning clips on top of each other is unpredictable (it varies between mono runtimes) | ||
|  |                 clip.GetParentTrack().SortClips(); | ||
|  | 
 | ||
|  |                 result = true; | ||
|  |             } | ||
|  | 
 | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void SetStart(TimelineClip clip, double time, bool affectTimeScale) | ||
|  |         { | ||
|  |             var supportsClipIn = clip.SupportsClipIn(); | ||
|  |             var supportsPadding = TimelineUtility.IsRecordableAnimationClip(clip); | ||
|  |             bool calculateTimeScale = (affectTimeScale && clip.SupportsSpeedMultiplier()); | ||
|  | 
 | ||
|  |             // treat empty recordable clips as not supporting clip in (there are no keys to modify) | ||
|  |             if (supportsPadding && (clip.animationClip == null || clip.animationClip.empty)) | ||
|  |             { | ||
|  |                 supportsClipIn = false; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (supportsClipIn && !supportsPadding && !calculateTimeScale) | ||
|  |             { | ||
|  |                 var minStart = clip.FromLocalTimeUnbound(0.0); | ||
|  |                 if (time < minStart) | ||
|  |                     time = minStart; | ||
|  |             } | ||
|  | 
 | ||
|  |             var maxStart = clip.end - TimelineClip.kMinDuration; | ||
|  |             if (time > maxStart) | ||
|  |                 time = maxStart; | ||
|  | 
 | ||
|  |             var timeOffset = time - clip.start; | ||
|  |             var duration = clip.duration - timeOffset; | ||
|  | 
 | ||
|  |             if (calculateTimeScale) | ||
|  |             { | ||
|  |                 var f = clip.duration / duration; | ||
|  |                 clip.timeScale *= f; | ||
|  |             } | ||
|  | 
 | ||
|  | 
 | ||
|  |             if (supportsClipIn && !calculateTimeScale) | ||
|  |             { | ||
|  |                 if (supportsPadding) | ||
|  |                 { | ||
|  |                     double clipInGlobal = clip.clipIn / clip.timeScale; | ||
|  |                     double keyShift = -timeOffset; | ||
|  |                     if (timeOffset < 0) // left drag, eliminate clipIn before shifting | ||
|  |                     { | ||
|  |                         double clipInDelta = Math.Max(-clipInGlobal, timeOffset); | ||
|  |                         keyShift = -Math.Min(0, timeOffset - clipInDelta); | ||
|  |                         clip.clipIn += clipInDelta * clip.timeScale; | ||
|  |                     } | ||
|  |                     else if (timeOffset > 0) // right drag, elimate padding in animation clip before adding clip in | ||
|  |                     { | ||
|  |                         var clipInfo = AnimationClipCurveCache.Instance.GetCurveInfo(clip.animationClip); | ||
|  |                         double keyDelta = clip.FromLocalTimeUnbound(clipInfo.keyTimes.Min()) - clip.start; | ||
|  |                         keyShift = -Math.Max(0, Math.Min(timeOffset, keyDelta)); | ||
|  |                         clip.clipIn += Math.Max(timeOffset + keyShift, 0) * clip.timeScale; | ||
|  |                     } | ||
|  |                     if (keyShift != 0) | ||
|  |                     { | ||
|  |                         AnimationTrackRecorder.ShiftAnimationClip(clip.animationClip, (float)(keyShift * clip.timeScale)); | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     clip.clipIn += timeOffset * clip.timeScale; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             clip.start = time; | ||
|  |             clip.duration = duration; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void SetEnd(TimelineClip clip, double time, bool affectTimeScale) | ||
|  |         { | ||
|  |             var duration = Math.Max(time - clip.start, TimelineClip.kMinDuration); | ||
|  | 
 | ||
|  |             if (affectTimeScale && clip.SupportsSpeedMultiplier()) | ||
|  |             { | ||
|  |                 var f = clip.duration / duration; | ||
|  |                 clip.timeScale *= f; | ||
|  |             } | ||
|  | 
 | ||
|  |             clip.duration = duration; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool ResetEditing(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             var result = false; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |                 result = result || ResetEditing(clip); | ||
|  | 
 | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool ResetEditing(TimelineClip clip) | ||
|  |         { | ||
|  |             if (clip.asset == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Editing")); | ||
|  | 
 | ||
|  |             clip.clipIn = 0.0; | ||
|  | 
 | ||
|  |             if (clip.clipAssetDuration < double.MaxValue) | ||
|  |             { | ||
|  |                 var duration = clip.clipAssetDuration / clip.timeScale; | ||
|  |                 TrimClipWithEditMode(clip, TrimEdge.End, clip.start + duration); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool MatchContent(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             var result = false; | ||
|  | 
 | ||
|  |             foreach (var clip in clips) | ||
|  |                 result |= MatchContent(clip); | ||
|  | 
 | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool MatchContent(TimelineClip clip) | ||
|  |         { | ||
|  |             if (clip.asset == null) | ||
|  |                 return false; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterClip(clip, L10n.Tr("Match Clip Content")); | ||
|  | 
 | ||
|  |             var newStartCandidate = clip.start - clip.clipIn / clip.timeScale; | ||
|  |             var newStart = newStartCandidate < 0.0 ? 0.0 : newStartCandidate; | ||
|  | 
 | ||
|  |             TrimClipWithEditMode(clip, TrimEdge.Start, newStart); | ||
|  | 
 | ||
|  |             // In case resetting the start was blocked by edit mode or timeline start, we do the best we can | ||
|  |             clip.clipIn = (clip.start - newStartCandidate) * clip.timeScale; | ||
|  |             if (clip.clipAssetDuration > 0 && TimelineHelpers.HasUsableAssetDuration(clip)) | ||
|  |             { | ||
|  |                 var duration = TimelineHelpers.GetLoopDuration(clip); | ||
|  |                 var offset = (clip.clipIn / clip.timeScale) % duration; | ||
|  |                 TrimClipWithEditMode(clip, TrimEdge.End, clip.start - offset + duration); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void TrimClipWithEditMode(TimelineClip clip, TrimEdge edge, double time) | ||
|  |         { | ||
|  |             var clipItem = ItemsUtils.ToItem(clip); | ||
|  |             EditMode.BeginTrim(clipItem, edge); | ||
|  |             if (edge == TrimEdge.Start) | ||
|  |                 EditMode.TrimStart(clipItem, time, false); | ||
|  |             else | ||
|  |                 EditMode.TrimEnd(clipItem, time, false); | ||
|  |             EditMode.FinishTrim(); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool CompleteLastLoop(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 CompleteLastLoop(clip); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void CompleteLastLoop(TimelineClip clip) | ||
|  |         { | ||
|  |             FixLoops(clip, true); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool TrimLastLoop(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 TrimLastLoop(clip); | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static void TrimLastLoop(TimelineClip clip) | ||
|  |         { | ||
|  |             FixLoops(clip, false); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void FixLoops(TimelineClip clip, bool completeLastLoop) | ||
|  |         { | ||
|  |             if (!TimelineHelpers.HasUsableAssetDuration(clip)) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             var loopDuration = TimelineHelpers.GetLoopDuration(clip); | ||
|  |             var firstLoopDuration = loopDuration - clip.clipIn * (1.0 / clip.timeScale); | ||
|  | 
 | ||
|  |             // Making sure we don't trim to zero | ||
|  |             if (!completeLastLoop && firstLoopDuration > clip.duration) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             var numLoops = (clip.duration - firstLoopDuration) / loopDuration; | ||
|  |             var numCompletedLoops = Math.Floor(numLoops); | ||
|  | 
 | ||
|  |             if (!(numCompletedLoops < numLoops)) | ||
|  |                 return; | ||
|  | 
 | ||
|  |             if (completeLastLoop) | ||
|  |                 numCompletedLoops += 1; | ||
|  | 
 | ||
|  |             var newEnd = clip.start + firstLoopDuration + loopDuration * numCompletedLoops; | ||
|  | 
 | ||
|  |             UndoExtensions.RegisterClip(clip, L10n.Tr("Trim Clip Last Loop")); | ||
|  | 
 | ||
|  |             TrimClipWithEditMode(clip, TrimEdge.End, newEnd); | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool DoubleSpeed(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 if (clip.SupportsSpeedMultiplier()) | ||
|  |                 { | ||
|  |                     UndoExtensions.RegisterClip(clip, L10n.Tr("Double Clip Speed")); | ||
|  |                     clip.timeScale = clip.timeScale * 2.0f; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool HalfSpeed(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 if (clip.SupportsSpeedMultiplier()) | ||
|  |                 { | ||
|  |                     UndoExtensions.RegisterClip(clip, L10n.Tr("Half Clip Speed")); | ||
|  |                     clip.timeScale = clip.timeScale * 0.5f; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  | 
 | ||
|  |         public static bool ResetSpeed(IEnumerable<TimelineClip> clips) | ||
|  |         { | ||
|  |             foreach (var clip in clips) | ||
|  |             { | ||
|  |                 if (clip.timeScale != 1.0) | ||
|  |                 { | ||
|  |                     UndoExtensions.RegisterClip(clip, L10n.Tr("Reset Clip Speed")); | ||
|  |                     clip.timeScale = 1.0; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return true; | ||
|  |         } | ||
|  |     } | ||
|  | } |