Editor/MovieMaker/BlockDisplay/CurveBlockItem.Paint.cs
using Sandbox.MovieMaker;
using Sandbox.MovieMaker.Compiled;
namespace Editor.MovieMaker.BlockDisplays;
#nullable enable
partial class CurveBlockItem<T>
{
private GraphicsLine[]? Lines { get; set; }
private IEnumerable<MovieTimeRange> GetPaintHints( MovieTimeRange timeRange )
{
return Block switch
{
IPaintHintBlock paintHintBlock => paintHintBlock.GetPaintHints( timeRange ),
CompiledSampleBlock<T> => [timeRange.Clamp( Block.TimeRange )],
_ => []
};
}
protected virtual void GetCurveTimes( List<MovieTime> times )
{
var timeRange = TimeRange;
times.Add( timeRange.Start );
void TryAddTime( MovieTime time )
{
time += Offset;
if ( time < timeRange.Start ) return;
if ( time > timeRange.End ) return;
if ( times[^1] < time )
{
times.Add( time );
}
}
foreach ( var hintRange in GetPaintHints( timeRange ) )
{
TryAddTime( hintRange.Start );
var clamped = hintRange with { End = hintRange.End - MovieTime.Epsilon };
foreach ( var time in clamped.GetSampleTimes( 30 ) )
{
TryAddTime( time );
}
TryAddTime( hintRange.End - MovieTime.Epsilon );
}
TryAddTime( timeRange.End );
}
private void UpdateRanges()
{
if ( Elements.Count < 1 ) return;
var times = Static.UpdateRanges_Times ??= new();
times.Clear();
GetCurveTimes( times );
if ( times.Count == 0 ) return;
Span<float> floats = stackalloc float[Elements.Count];
for ( var j = 0; j < Elements.Count; ++j )
{
_ranges[j] = (Elements[j].Min ?? float.PositiveInfinity, Elements[j].Max ?? float.NegativeInfinity);
}
foreach ( var t in times )
{
if ( t.IsNegative ) continue;
var value = Block.GetValue( t - Offset );
Decompose( value, floats );
for ( var j = 0; j < Elements.Count; ++j )
{
_ranges[j] = (Math.Min( _ranges[j].Min, floats[j] ), Math.Max( _ranges[j].Max, floats[j] ));
}
}
}
protected override void OnPaint()
{
base.OnPaint();
if ( Elements.Count < 1 ) return;
var paintHash = PaintHash;
if ( paintHash == _lastPaintHash ) return;
_lastPaintHash = paintHash;
if ( Lines is null )
{
Lines = new GraphicsLine[Elements.Count];
for ( var i = 0; i < Elements.Count; ++i )
{
Lines[i] = new CurveLine( this, Elements[i].Color );
}
}
foreach ( var line in Lines )
{
line.Clear();
}
var times = Static.PaintCurve_Times ??= new();
times.Clear();
GetCurveTimes( times );
if ( times.Count == 0 ) return;
var margin = 2f;
var height = LocalRect.Height - margin * 2f;
Span<float> floats = stackalloc float[Elements.Count];
Span<(float Min, float Max)> ranges = stackalloc (float, float)[Elements.Count];
Span<float> mids = stackalloc float[Elements.Count];
_ranges.CopyTo( ranges );
var range = 0f;
// All previews on the same track should have the same range
foreach ( var preview in Parent.BlockItems )
{
if ( preview is not ICurveBlockItem { Ranges: { } curveRanges } ) continue;
if ( curveRanges.Count != ranges.Length ) continue;
for ( var j = 0; j < Elements.Count; ++j )
{
ranges[j] = (Math.Min( ranges[j].Min, curveRanges[j].Min ), Math.Max( ranges[j].Max, curveRanges[j].Max ));
}
}
for ( var j = 0; j < Elements.Count; ++j )
{
range = Math.Max( range, ranges[j].Max - ranges[j].Min );
mids[j] = (ranges[j].Min + ranges[j].Max) * 0.5f;
}
for ( var j = 0; j < Elements.Count; ++j )
{
Lines[j].Clear();
Lines[j].PrepareGeometryChange();
Lines[j].Position = new Vector2( 0f, margin );
Lines[j].Size = new Vector2( LocalRect.Width, height );
}
var scale = range <= 0f ? 0f : height / range;
// Second pass, update lines
var t0 = TimeRange.Start;
var t1 = TimeRange.End;
var dxdt = LocalRect.Width / (t1 - t0).TotalSeconds;
foreach ( var t in times )
{
var value = Block.GetValue( t - Offset );
var x = LocalRect.Left + (float)((t - t0).TotalSeconds * dxdt);
Decompose( value, floats );
for ( var j = 0; j < Elements.Count; ++j )
{
var y = (mids[j] - floats[j]) * scale + 0.5f * height;
if ( t <= Block.TimeRange.Start )
{
Lines[j].MoveTo( new Vector2( x, y ) );
}
else
{
Lines[j].LineTo( new Vector2( x, y ) );
}
}
}
}
}
file class Static
{
[field: ThreadStatic]
public static List<MovieTime>? PaintCurve_Times { get; set; }
[field: ThreadStatic]
public static List<MovieTime>? UpdateRanges_Times { get; set; }
}
file class CurveLine : GraphicsLine
{
public Color Color { get; }
public CurveLine( GraphicsItem parent, Color color )
: base( parent )
{
Color = color;
}
protected override void OnPaint()
{
Paint.SetPen( Color.WithAlpha( 0.25f ), 2f );
PaintLine();
}
}