Files
2026-07-13 13:13:17 +08:00

273 lines
9.0 KiB
C#

#nullable enable
using T3.Core.Animation;
using T3.Core.DataTypes;
using T3.Core.Operator;
using T3.Editor.UiModel.Commands;
using T3.Editor.UiModel.Commands.Animation;
namespace T3.Editor.Gui.Windows.TimeLine;
/// <summary>
/// Piecewise-linear time-warp drag engine. Powers three interactions:
/// <list type="bullet">
/// <item>Dragging an inner TimeWarp handle (two-segment remap around it).</item>
/// <item>Dragging an SRI edge when inner handles exist (single-segment remap against the nearest handle).</item>
/// <item>Middle-translate when handles exist (pure translation of keys, clips, and handles).</item>
/// </list>
/// Builds a <see cref="MacroCommand"/> on completion containing the keyframe change,
/// the clip-move, and optionally a <see cref="SetTimeWarpHandlesCommand"/> so undo/redo
/// restores handle positions along with the retimed elements.
/// </summary>
internal sealed class TimeWarpDrag
{
public TimeWarpDrag(TimeLineCanvas canvas, TimeWarpHandleSet handles)
{
_canvas = canvas;
_handles = handles;
}
public bool IsActive { get; private set; }
/// <summary>Start a drag. <paramref name="origHandleU"/> is the moving anchor; fixed boundaries pin the segment ends.</summary>
public void Begin(Instance composition,
double origHandleU,
double prevBoundary,
double nextBoundary,
bool singleSegment,
double segmentLeftU,
double segmentRightU,
bool pureTranslation,
bool trackHandlePositions,
bool useAllKeyframes)
{
Reset();
_origHandleU = origHandleU;
_prev = prevBoundary;
_next = nextBoundary;
_singleSegment = singleSegment;
_segLeftU = segmentLeftU;
_segRightU = segmentRightU;
_pureTranslation = pureTranslation;
if (trackHandlePositions)
{
_handles.CopyTo(_origHandles);
_handlesCommand = new SetTimeWarpHandlesCommand(_handles, _origHandles);
}
// Snapshot keyframes that are in the affected range (or all, for pure translation),
// aggregated across every active keyframe editor (DopeSheet + CurveEditor when split).
var editors = _canvas.KeyframeEditors;
var source = useAllKeyframes
? editors.EnumerateAllKeyframes()
: editors.EnumerateAllSelectedKeyframes();
foreach (var def in source)
{
if (!pureTranslation && !InsideAffectedRange(def.U))
continue;
_keys.Add(def);
_keyOrigU.Add(def.U);
}
if (_keys.Count > 0)
{
editors.CopyAllCurvesTo(_curves);
_keyframesCommand = new ChangeKeyframesCommand(_keys, _curves);
}
// Clips: retime start/end of each selected clip.
foreach (var clip in _canvas.ClipArea.EnumerateSelectedClips())
{
_clips.Add(clip);
_clipOrigStart.Add(clip.TimeRange.Start);
_clipOrigEnd.Add(clip.TimeRange.End);
}
if (_clips.Count > 0)
{
_clipBuffer.Clear();
for (var i = 0; i < _clips.Count; i++)
_clipBuffer.Add(_clips[i]);
_clipsCommand = new MoveTimeClipsCommand(composition, _clipBuffer);
}
IsActive = true;
}
/// <summary>Apply this frame's remap. <paramref name="currentU"/> is the moving anchor's new U.</summary>
public void Update(double currentU)
{
if (!IsActive)
return;
if (_pureTranslation)
{
var du = currentU - _origHandleU;
for (var i = 0; i < _keys.Count; i++)
_keys[i].U = _keyOrigU[i] + du;
for (var i = 0; i < _clips.Count; i++)
{
var clip = _clips[i];
clip.TimeRange.Start = _clipOrigStart[i] + (float)du;
clip.TimeRange.End = _clipOrigEnd[i] + (float)du;
}
if (_handlesCommand != null)
{
for (var i = 0; i < _origHandles.Count && i < _handles.Count; i++)
_handles.SetValueUnsorted(i, _origHandles[i] + du);
}
AnimationParameterEditing.CurvesTablesNeedsRefresh = true;
return;
}
for (var i = 0; i < _keys.Count; i++)
_keys[i].U = RemapPiecewise(_keyOrigU[i], currentU);
for (var i = 0; i < _clips.Count; i++)
{
var clip = _clips[i];
var newStart = (float)RemapPiecewise(_clipOrigStart[i], currentU);
var newEnd = (float)RemapPiecewise(_clipOrigEnd[i], currentU);
if (newEnd - newStart < 1 / 60f)
newEnd = newStart + 1 / 60f;
clip.TimeRange.Start = newStart;
clip.TimeRange.End = newEnd;
}
AnimationParameterEditing.CurvesTablesNeedsRefresh = true;
}
/// <summary>Finalize and push the macro command onto the undo stack.</summary>
public void Complete()
{
if (!IsActive)
return;
if (_keys.Count == 0 && _clips.Count == 0 && _handlesCommand == null)
{
Reset();
return;
}
var commands = new List<ICommand>(3);
if (_keyframesCommand != null)
{
_keyframesCommand.StoreCurrentValues();
commands.Add(_keyframesCommand);
}
if (_clipsCommand != null)
{
_clipsCommand.StoreCurrentValues();
commands.Add(_clipsCommand);
}
if (_handlesCommand != null)
{
_handlesCommand.StoreCurrentValues(_handles);
commands.Add(_handlesCommand);
}
if (commands.Count > 0)
{
var macro = new MacroCommand("TimeWarp", commands);
UndoRedoStack.AddAndExecute(macro);
}
Reset();
}
private bool InsideAffectedRange(double u)
{
if (_singleSegment)
return u >= _segLeftU && u <= _segRightU;
return u >= _prev && u <= _next;
}
private double RemapPiecewise(double t, double newU)
{
var origU = _origHandleU;
if (_singleSegment)
{
// One-segment warp: either segLeft or segRight coincides with origU (the moving point).
if (t < _segLeftU || t > _segRightU) return t;
if (Math.Abs(_segLeftU - origU) < 1e-9)
{
// Left end is moving; right end fixed.
var right = _segRightU;
var lenOrig = right - origU;
if (Math.Abs(lenOrig) < 1e-9) return t;
var ratio = (t - origU) / lenOrig;
return newU + ratio * (right - newU);
}
else
{
// Right end is moving; left end fixed.
var left = _segLeftU;
var lenOrig = origU - left;
if (Math.Abs(lenOrig) < 1e-9) return t;
var ratio = (t - left) / lenOrig;
return left + ratio * (newU - left);
}
}
// Two-segment warp: (prev, origU) and (origU, next).
if (t < _prev || t > _next) return t;
if (t <= origU)
{
var lenOrig = origU - _prev;
if (Math.Abs(lenOrig) < 1e-9) return t;
var ratio = (t - _prev) / lenOrig;
return _prev + ratio * (newU - _prev);
}
else
{
var lenOrig = _next - origU;
if (Math.Abs(lenOrig) < 1e-9) return t;
var ratio = (t - origU) / lenOrig;
return newU + ratio * (_next - newU);
}
}
private void Reset()
{
_keys.Clear();
_keyOrigU.Clear();
_clips.Clear();
_clipOrigStart.Clear();
_clipOrigEnd.Clear();
_origHandles.Clear();
_keyframesCommand = null;
_clipsCommand = null;
_handlesCommand = null;
_pureTranslation = false;
_singleSegment = false;
IsActive = false;
}
private readonly TimeLineCanvas _canvas;
private readonly TimeWarpHandleSet _handles;
private double _origHandleU;
private double _prev;
private double _next;
private bool _singleSegment;
private double _segLeftU;
private double _segRightU;
private bool _pureTranslation;
private readonly List<VDefinition> _keys = new(64);
private readonly List<double> _keyOrigU = new(64);
private readonly List<TimeClip> _clips = new(16);
private readonly List<float> _clipOrigStart = new(16);
private readonly List<float> _clipOrigEnd = new(16);
private readonly List<double> _origHandles = new(8);
private readonly List<Curve> _curves = new(32);
private readonly List<TimeClip> _clipBuffer = new(16);
private ChangeKeyframesCommand? _keyframesCommand;
private MoveTimeClipsCommand? _clipsCommand;
private SetTimeWarpHandlesCommand? _handlesCommand;
}