mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-05-23 22:36:16 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
261 lines
5.8 KiB
C#
261 lines
5.8 KiB
C#
using Sandbox.MovieMaker;
|
|
|
|
namespace Editor.MovieMaker.BlockDisplays;
|
|
|
|
#nullable enable
|
|
|
|
/// <summary>
|
|
/// A <see cref="CurveBlockItem{T}"/> is broken up into fixed-width tiles that get repainted separately.
|
|
/// </summary>
|
|
public sealed class CurveTile : GraphicsItem
|
|
{
|
|
public const int TileWidth = 256;
|
|
|
|
private readonly Pixmap _pixmap = new( TileWidth, Timeline.BlockHeight );
|
|
private readonly List<Vector2>[] _lines;
|
|
|
|
private readonly (float Min, float Max)[] _ranges;
|
|
|
|
/// <summary>
|
|
/// Min and max value for each decomposed element within this tile's time range.
|
|
/// Will always be up to date.
|
|
/// </summary>
|
|
public IReadOnlyList<(float Min, float Max)> Ranges
|
|
{
|
|
get
|
|
{
|
|
UpdateRanges();
|
|
return _ranges;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parent <see cref="CurveBlockItem{T}"/>.
|
|
/// </summary>
|
|
public ICurveBlockItem BlockItem { get; }
|
|
|
|
/// <summary>
|
|
/// Which tile is this in the parent block? First tile is index 0, next is 1, etc.
|
|
/// </summary>
|
|
public int Index { get; }
|
|
|
|
public MovieTimeRange TimeRange { get; set; }
|
|
|
|
private MovieTimeRange _pixmapTimeRange;
|
|
private MovieTimeRange _rangesTimeRange;
|
|
|
|
public CurveTile( ICurveBlockItem blockItem, int index )
|
|
: base( (GraphicsItem)blockItem )
|
|
{
|
|
BlockItem = blockItem;
|
|
Index = index;
|
|
|
|
Height = Timeline.BlockHeight;
|
|
|
|
var elementCount = blockItem.Elements.Count;
|
|
|
|
_lines = new List<Vector2>[elementCount];
|
|
_ranges = new (float Min, float Max)[elementCount];
|
|
|
|
for ( var i = 0; i < elementCount; ++i )
|
|
{
|
|
_lines[i] = [];
|
|
}
|
|
}
|
|
|
|
public void MarkDirty( MovieTimeRange timeRange )
|
|
{
|
|
if ( timeRange.Intersect( TimeRange ) is not { IsEmpty: false } clamped )
|
|
{
|
|
return;
|
|
}
|
|
|
|
_pixmapTimeRange = default;
|
|
_rangesTimeRange = default;
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
UpdatePixmap();
|
|
|
|
var scale = LocalRect.Width / _pixmap.Width;
|
|
|
|
var clampedRect = LocalRect;
|
|
|
|
clampedRect.Right = Math.Min( clampedRect.Right, Parent.Width - Position.x );
|
|
|
|
Paint.ClearPen();
|
|
Paint.Scale( scale, 1f );
|
|
Paint.SetBrush( _pixmap );
|
|
Paint.DrawRect( clampedRect with { Left = clampedRect.Left / scale, Right = clampedRect.Right / scale } );
|
|
}
|
|
|
|
[field: ThreadStatic]
|
|
public static List<MovieTime>? UpdatePixmap_Times { get; set; }
|
|
|
|
private void UpdatePixmap()
|
|
{
|
|
if ( _pixmapTimeRange == TimeRange ) return;
|
|
|
|
_pixmapTimeRange = TimeRange;
|
|
_pixmap.Clear( Color.Transparent );
|
|
|
|
var elementCount = BlockItem.Elements.Count;
|
|
|
|
if ( elementCount <= 0 ) return;
|
|
|
|
var times = UpdatePixmap_Times ??= new List<MovieTime>();
|
|
times.Clear();
|
|
|
|
GetCurveTimes( times );
|
|
|
|
if ( times.Count == 0 ) return;
|
|
|
|
var ranges = BlockItem.Ranges;
|
|
|
|
for ( var i = 0; i < elementCount; ++i )
|
|
{
|
|
_lines[i].Clear();
|
|
}
|
|
|
|
const float margin = 2f;
|
|
var height = LocalRect.Height - margin * 2f;
|
|
|
|
Span<float> mids = stackalloc float[elementCount];
|
|
|
|
var range = 0f;
|
|
|
|
for ( var j = 0; j < elementCount; ++j )
|
|
{
|
|
range = Math.Max( range, ranges[j].Max - ranges[j].Min );
|
|
|
|
mids[j] = (ranges[j].Min + ranges[j].Max) * 0.5f;
|
|
}
|
|
|
|
var scale = range <= 0f ? 0f : height / range;
|
|
|
|
var t0 = TimeRange.Start;
|
|
var t1 = TimeRange.End;
|
|
|
|
var width = _pixmap.Width;
|
|
var dxdt = width / (t1 - t0).TotalSeconds;
|
|
|
|
Span<float> floats = stackalloc float[elementCount];
|
|
|
|
foreach ( var t in times )
|
|
{
|
|
BlockItem.Read( t - BlockItem.Offset, floats );
|
|
|
|
var x = (float)((t - t0).TotalSeconds * dxdt);
|
|
|
|
for ( var j = 0; j < elementCount; ++j )
|
|
{
|
|
var y = (mids[j] - floats[j]) * scale + 0.5f * height + margin;
|
|
|
|
_lines[j].Add( new Vector2( x, y ) );
|
|
}
|
|
}
|
|
|
|
using ( Paint.ToPixmap( _pixmap ) )
|
|
{
|
|
Paint.Antialiasing = true;
|
|
|
|
for ( var i = 0; i < elementCount; ++i )
|
|
{
|
|
Paint.SetPen( BlockItem.Elements[i].Color.WithAlpha( 0.25f ), 2f );
|
|
Paint.DrawLine( _lines[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
[field: ThreadStatic]
|
|
public static List<MovieTime>? UpdateRanges_Times { get; set; }
|
|
|
|
private void UpdateRanges()
|
|
{
|
|
if ( _rangesTimeRange == TimeRange ) return;
|
|
|
|
_rangesTimeRange = TimeRange;
|
|
|
|
var elementCount = BlockItem.Elements.Count;
|
|
|
|
if ( elementCount <= 0 ) return;
|
|
|
|
for ( var i = 0; i < elementCount; ++i )
|
|
{
|
|
_ranges[i].Min = BlockItem.Elements[i].Min ?? float.PositiveInfinity;
|
|
_ranges[i].Max = BlockItem.Elements[i].Max ?? float.NegativeInfinity;
|
|
}
|
|
|
|
var times = UpdateRanges_Times ??= new List<MovieTime>();
|
|
times.Clear();
|
|
|
|
GetCurveTimes( times );
|
|
|
|
if ( times.Count == 0 ) return;
|
|
|
|
Span<float> floats = stackalloc float[elementCount];
|
|
|
|
foreach ( var t in times )
|
|
{
|
|
if ( t < _rangesTimeRange.Start ) continue;
|
|
if ( t > _rangesTimeRange.End ) break;
|
|
|
|
BlockItem.Read( t - BlockItem.Offset, floats );
|
|
|
|
for ( var i = 0; i < elementCount; ++i )
|
|
{
|
|
_ranges[i].Min = Math.Min( _ranges[i].Min, floats[i] );
|
|
_ranges[i].Max = Math.Max( _ranges[i].Max, floats[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls curve resolution based on zoom level.
|
|
/// </summary>
|
|
[FromTheme]
|
|
public static float PixelsPerSample { get; set; } = 2f;
|
|
|
|
private void GetCurveTimes( List<MovieTime> times )
|
|
{
|
|
var timeRange = TimeRange;
|
|
|
|
times.Add( timeRange.Start );
|
|
|
|
void TryAddTime( MovieTime time )
|
|
{
|
|
time += BlockItem.Offset;
|
|
|
|
if ( time < timeRange.Start ) return;
|
|
if ( time > timeRange.End ) return;
|
|
|
|
if ( times[^1] < time )
|
|
{
|
|
times.Add( time );
|
|
}
|
|
}
|
|
|
|
var sampleRate = Math.Max( (int)Math.Round( TileWidth / (TimeRange.Duration.TotalSeconds * PixelsPerSample) ), 1 );
|
|
|
|
foreach ( var hintRange in BlockItem.GetPaintHints( timeRange ) )
|
|
{
|
|
TryAddTime( hintRange.Start );
|
|
|
|
// We subtract epsilon from the end so we can more accurately see
|
|
// sudden value changes going into the next paint hint time range
|
|
|
|
var clamped = hintRange with { End = hintRange.End - MovieTime.Epsilon };
|
|
|
|
foreach ( var time in clamped.GetSampleTimes( sampleRate ) )
|
|
{
|
|
TryAddTime( time );
|
|
}
|
|
|
|
TryAddTime( hintRange.End - MovieTime.Epsilon );
|
|
}
|
|
|
|
TryAddTime( timeRange.End );
|
|
}
|
|
}
|