UnityGame/Library/PackageCache/com.unity.timeline/Editor/Utilities/ClipModifier.cs
2024-10-27 10:53:47 +03:00

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;
}
}
}