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

165 lines
5.0 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using T3.Core.Logging;
using T3.Core.Operator.Slots;
using T3.Core.Utils;
using T3.Serialization;
namespace T3.Core.Animation;
/// <summary>
/// Maps are timeline region to a source time region and contains some additional attributes for display in timeline editor.
/// </summary>
public sealed class TimeClip : IOutputData
{
// Used when creating new timeClips
// ReSharper disable once MemberCanBePrivate.Global
public TimeClip()
{
var t = Playback.Current != null
? (float)Playback.Current.TimeInBars
: 0;
TimeRange = new TimeRange(t, t + DefaultClipDuration);
SourceRange = new TimeRange(t, t + DefaultClipDuration);
}
private const float DefaultClipDuration = 4f;
public Guid Id { get; set; }
public TimeRange TimeRange;
public TimeRange SourceRange;
public int LayerIndex { get; set; } = 0;
/// <summary>
/// TimeClips that primary purpose is use with nested content with remapping local time to that
/// content time. Operators like TimeClipSwitch can clear this flag to indicate that the source
/// region should be linked to the clip region when dragging clips in the timeline.
/// </summary>
public bool UsedForRegionMapping = true;
public Type DataType => typeof(TimeClip);
[JsonIgnore]
public float Speed => MathF.Abs(TimeRange.Duration) < 0.001f ? 1 : SourceRange.Duration / TimeRange.Duration;
public bool MakeConform()
{
var neededFix = false;
if (!TimeRange.Start._IsFinite())
{
TimeRange.Start = 0;
neededFix = true;
}
if (!TimeRange.End._IsFinite())
{
TimeRange.End = TimeRange.Start + DefaultClipDuration;
neededFix = true;
}
if (!SourceRange.Start._IsFinite())
{
SourceRange.Start = TimeRange.Start;
neededFix = true;
}
if (!SourceRange.End._IsFinite())
{
SourceRange.End = TimeRange.End;
neededFix = true;
}
return neededFix;
}
public bool IsClipOverlappingOthers(IEnumerable<TimeClip> allTimeClips)
{
foreach (var otherClip in allTimeClips)
{
if (otherClip == this)
continue;
if (LayerIndex != otherClip.LayerIndex)
continue;
var start = TimeRange.Start;
var end = TimeRange.End;
var otherStart = otherClip.TimeRange.Start;
var otherEnd = otherClip.TimeRange.End;
if (otherEnd <= start || otherStart >= end)
continue;
return true;
}
return false;
}
#region serialization
public void ToJson(JsonTextWriter writer)
{
writer.WritePropertyName("TimeClip");
writer.WriteStartObject();
writer.WritePropertyName("TimeRange");
writer.WriteStartObject();
writer.WriteValue("Start", TimeRange.Start);
writer.WriteValue("End", TimeRange.End);
writer.WriteEndObject();
writer.WritePropertyName("SourceRange");
writer.WriteStartObject();
writer.WriteValue("Start", SourceRange.Start);
writer.WriteValue("End", SourceRange.End);
writer.WriteEndObject();
writer.WriteValue("LayerIndex", LayerIndex);
writer.WriteEndObject();
}
public void ReadFromJson(JToken json)
{
var timeClip = json["TimeClip"];
if (timeClip == null)
return;
var timeRange = timeClip["TimeRange"];
if (timeRange != null)
TimeRange = new TimeRange(timeRange.Value<float>("Start"), timeRange.Value<float>("End"));
var sourceRange = timeClip["SourceRange"];
if (sourceRange != null)
SourceRange = new TimeRange(sourceRange.Value<float>("Start"), sourceRange.Value<float>("End"));
LayerIndex = timeClip.Value<int>("LayerIndex");
}
#endregion
public bool Assign(IOutputData outputData)
{
if (outputData is TimeClip otherTimeClip)
{
TimeRange = otherTimeClip.TimeRange;
SourceRange = otherTimeClip.SourceRange;
LayerIndex = otherTimeClip.LayerIndex;
return true;
}
Log.Error($"Trying to assign output data of type '{outputData.GetType()}' to 'TimeClip'.");
return false;
}
public TimeClip Clone()
{
return new TimeClip
{
Id = Guid.NewGuid(),
TimeRange = this.TimeRange,
SourceRange = this.SourceRange,
LayerIndex = this.LayerIndex
};
}
}