Code/Utility/MovieTimeRange.cs
using System;
using System.Text.Json.Serialization;
namespace Sandbox.MovieMaker;
#nullable enable
/// <summary>
/// Represents a segment of time, given by <see cref="Start"/> and <see cref="End"/> times.
/// </summary>
/// <param name="Start">Minimum time in the range.</param>
/// <param name="End">Maximum time in the range.</param>
public readonly record struct MovieTimeRange( MovieTime Start, MovieTime End )
{
[JsonIgnore]
public MovieTime Duration => End - Start;
[JsonIgnore]
public MovieTime Center => MovieTime.Lerp( Start, End, 0.5 );
[JsonIgnore]
public bool IsEmpty => Start >= End;
public MovieTimeRange? Intersect( MovieTimeRange other )
{
if ( Start > other.End || End < other.Start ) return null;
return new MovieTimeRange( MovieTime.Max( Start, other.Start ), MovieTime.Min( End, other.End ) );
}
public MovieTimeRange Union( MovieTimeRange? other )
{
return other is { } value
? new MovieTimeRange( MovieTime.Min( Start, value.Start ), MovieTime.Max( End, value.End ) )
: this;
}
public MovieTimeRange Clamp( MovieTimeRange? range )
{
return new MovieTimeRange( Start.Clamp( range ), End.Clamp( range ) );
}
public MovieTimeRange ClampStart( MovieTime? start )
{
return start is { } value
? new MovieTimeRange( MovieTime.Max( value, Start ), MovieTime.Max( value, End ) )
: this;
}
public MovieTimeRange ClampEnd( MovieTime? end )
{
return end is { } value
? new MovieTimeRange( MovieTime.Min( value, Start ), MovieTime.Min( value, End ) )
: this;
}
public MovieTimeRange Grow( MovieTime startEndDelta ) => Grow( startEndDelta, startEndDelta );
public MovieTimeRange Grow( MovieTime startDelta, MovieTime endDelta )
{
if ( startDelta.IsZero && endDelta.IsZero ) return this;
var start = Start - startDelta;
var end = End + endDelta;
if ( end >= start )
{
return new MovieTimeRange( start, end );
}
// Work out how far start / end can move before they cross
var t = (Start - End).Ticks / (double)(startDelta + endDelta).Ticks;
return MovieTime.Lerp( start, start + startDelta, t );
}
public bool Contains( MovieTime time ) => time >= Start && time <= End;
public bool Contains( MovieTimeRange timeRange ) => timeRange.Start >= Start && timeRange.End <= End;
public float GetFraction( MovieTime time ) => Duration.GetFraction( time - Start );
public IEnumerable<MovieTime> GetSampleTimes( int sampleRate ) =>
GetSampleTimes( Start, Duration.GetFrameCount( sampleRate ), sampleRate );
public IEnumerable<MovieTime> GetSampleTimes( MovieTime firstSampleTime, int sampleCount, int sampleRate )
{
var firstIndex = Math.Max( 0, (Start - firstSampleTime).GetFrameIndex( sampleRate ) );
var lastIndex = Math.Min( sampleCount, (End - firstSampleTime).GetFrameCount( sampleRate ) );
return Enumerable.Range( firstIndex, lastIndex - firstIndex )
.Select( i => firstSampleTime + MovieTime.FromFrames( i, sampleRate ) );
}
public override string ToString() => $"[{Start}, {End}]";
#region Operators
public static implicit operator MovieTimeRange( MovieTime time ) => new( time, time );
public static implicit operator MovieTimeRange( (MovieTime, MovieTime) tuple ) => new( tuple.Item1, tuple.Item2 );
public static MovieTimeRange operator +( MovieTimeRange range, MovieTime offset ) => (range.Start + offset, range.End + offset);
public static MovieTimeRange operator -( MovieTimeRange range, MovieTime offset ) => (range.Start - offset, range.End - offset);
#endregion
}