Files
sbox-public/engine/Sandbox.System/UI/Styles/PanelTransform.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

381 lines
8.8 KiB
C#

using System;
using System.Collections.Immutable;
namespace Sandbox.UI
{
[SkipHotload]
public partial struct PanelTransform
{
// Considering how many entries these are gonna have in reality
// we might be best to just hardcode like 6 entries
public ImmutableList<Entry> List;
public Matrix BuildTransform( float width, float height, Vector2 perspectiveOrigin )
{
var tx = Matrix.Identity;
if ( List == null )
return tx;
foreach ( var e in List )
{
Matrix m = Matrix.Identity;
if ( e.Type == EntryType.Perspective )
{
m *= Matrix.CreateTranslation( new Vector3( perspectiveOrigin, 0.0f ) );
m *= e.ToMatrix( width, height );
m *= Matrix.CreateTranslation( new Vector3( -perspectiveOrigin, 0.0f ) );
}
else
{
m = e.ToMatrix( width, height );
}
tx = m * tx;
}
return tx;
}
/// <summary>
/// Returns true if this is empty.
/// </summary>
public bool IsEmpty()
{
return Entries == 0;
}
public readonly int Entries => List?.Count ?? 0;
readonly Entry GetEntry( int i )
{
if ( i >= Entries ) return new Entry { Type = EntryType.Invalid };
return List[i];
}
readonly Entry GetEntry( EntryType type )
{
if ( List == null )
return new Entry { Type = EntryType.Invalid };
foreach ( var e in List )
{
if ( e.Type == type )
return e;
}
return new Entry { Type = EntryType.Invalid };
}
public bool AddTranslate( Length? lengthX, Length? lengthY, Length? lengthZ = null )
{
if ( !lengthX.HasValue || !lengthY.HasValue )
return false;
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Translate, X = lengthX.Value, Y = lengthY.Value, Z = lengthZ ?? new Length() } );
return true;
}
public bool AddTranslateX( Length? length )
{
if ( !length.HasValue )
return false;
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Translate, X = length.Value } );
return true;
}
public bool AddTranslateY( Length? length )
{
if ( !length.HasValue )
return false;
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Translate, Y = length.Value } );
return true;
}
public bool AddTranslateZ( Length? length )
{
if ( !length.HasValue )
return false;
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Translate, Z = length.Value } );
return true;
}
public bool AddScale( float scale )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Scale, Data = Vector3.One * scale } );
return true;
}
public bool AddScale( Vector3 scale )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Scale, Data = scale } );
return true;
}
public bool AddSkew( float x, float y, float z )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Skew, Data = new Vector3( x, y, z ) } );
return true;
}
public bool AddRotation( float x, float y, float z )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Rotation, Data = new Vector3( x, y, z ) } );
return true;
}
public bool AddRotation( Vector3 angles ) => AddRotation( angles.x, angles.y, angles.z );
public bool AddMatrix3D( Matrix matrix )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Matrix, Matrix = matrix } );
return true;
}
public bool AddPerspective( Length d )
{
if ( List == null )
List = ImmutableList.Create<Entry>();
List = List.Add( new Entry { Type = EntryType.Perspective, X = d } );
return true;
}
internal static PanelTransform? Lerp( PanelTransform? a, PanelTransform? b, float delta, Vector2? dimensions = null )
{
var ma = a ?? new PanelTransform();
var mb = b ?? new PanelTransform();
var entries = Math.Max( ma.Entries, mb.Entries );
if ( entries <= 0 ) return new PanelTransform();
var builder = ImmutableList.CreateBuilder<Entry>();
for ( int i = 0; i < ma.Entries; i++ )
{
var from = ma.GetEntry( i );
var to = mb.GetEntry( from.Type );
if ( to.Type == EntryType.Invalid )
to = from.GetDefault();
if ( dimensions != null )
builder.Add( Entry.Lerp( from, to, delta, dimensions.Value ) );
else
builder.Add( Entry.Lerp( from, to, delta ) );
}
for ( int i = 0; i < mb.Entries; i++ )
{
var to = mb.GetEntry( i );
var from = ma.GetEntry( to.Type );
if ( from.Type != EntryType.Invalid )
continue;
if ( dimensions != null )
builder.Add( Entry.Lerp( to.GetDefault(), to, delta, dimensions.Value ) );
else
builder.Add( Entry.Lerp( to.GetDefault(), to, delta ) );
}
var o = new PanelTransform();
o.List = builder.ToImmutable();
return o;
}
internal readonly PanelTransform GetScaled( float scale )
{
if ( Entries <= 0 ) return new PanelTransform();
var builder = ImmutableList.CreateBuilder<Entry>();
for ( int i = 0; i < Entries; i++ )
{
var e = GetEntry( i );
Length.Scale( ref e.X, scale );
Length.Scale( ref e.Y, scale );
Length.Scale( ref e.Z, scale );
builder.Add( e );
}
var o = new PanelTransform();
o.List = builder.ToImmutable();
return o;
}
public override bool Equals( object obj )
{
return obj is PanelTransform transform &&
EqualityComparer<ImmutableList<Entry>>.Default.Equals( List, transform.List );
}
public readonly override int GetHashCode()
{
return HashCode.Combine( List );
}
public static bool operator ==( PanelTransform a, PanelTransform b )
{
return a.Equals( b );
}
public static bool operator !=( PanelTransform a, PanelTransform b )
{
return !a.Equals( b );
}
public struct Entry
{
public EntryType Type;
public Vector3 Data;
public Matrix Matrix;
public Length X;
public Length Y;
public Length Z;
internal static Entry Lerp( Entry a, Entry b, float delta )
{
var data = a.Type == EntryType.Rotation || a.Type == EntryType.Skew ?
Angles.Lerp( new Angles( a.Data ), new Angles( b.Data ), delta ).AsVector3() :
Vector3.Lerp( a.Data, b.Data, delta, false );
return new Entry
{
Type = a.Type,
Data = data,
Matrix = Matrix.Lerp( a.Matrix, b.Matrix, delta ),
X = Length.Lerp( a.X, b.X, delta ) ?? b.X,
Y = Length.Lerp( a.Y, b.Y, delta ) ?? b.Y,
Z = Length.Lerp( a.Z, b.Z, delta ) ?? b.Z,
};
}
internal static Entry Lerp( Entry a, Entry b, float delta, Vector2 dimensions )
{
var data = a.Type == EntryType.Rotation || a.Type == EntryType.Skew ?
Angles.Lerp( new Angles( a.Data ), new Angles( b.Data ), delta ).AsVector3() :
Vector3.Lerp( a.Data, b.Data, delta, false );
return new Entry
{
Type = a.Type,
Data = data,
Matrix = Matrix.Lerp( a.Matrix, b.Matrix, delta ),
X = Length.Lerp( a.X, b.X, delta, dimensions.x ) ?? b.X,
Y = Length.Lerp( a.Y, b.Y, delta, dimensions.y ) ?? b.Y,
Z = Length.Lerp( a.Z, b.Z, delta ) ?? b.Z,
};
}
public Matrix ToMatrix( float width, float height )
{
switch ( Type )
{
case EntryType.Rotation:
{
return Matrix.CreateRotation( Data );
}
case EntryType.Scale:
{
return Matrix.CreateScale( Data );
}
case EntryType.Translate:
{
return Matrix.CreateTranslation( new Vector3( X.GetPixels( width ), Y.GetPixels( height ), Z.GetPixels( 0 ) ) );
}
case EntryType.Skew:
{
var ax = MathF.Tan( Data.x.DegreeToRadian() );
var ay = MathF.Tan( Data.y.DegreeToRadian() );
return Matrix.CreateMatrix3D( [
1.0f, ay, 0.0f, 0.0f,
ax, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
] );
}
case EntryType.Matrix:
{
return Matrix;
}
case EntryType.Perspective:
{
return Matrix.CreateMatrix3D( [
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, -1.0f / MathF.Max(X.GetPixels( width ), 1.0f),
0.0f, 0.0f, 0.0f, 1.0f
] );
}
default:
return Matrix.Identity;
}
}
public Entry GetDefault()
{
return new Entry
{
Type = this.Type,
Data = Vector3.One,
Matrix = Matrix.Identity,
X = new Length { Unit = X.Unit },
Y = new Length { Unit = Y.Unit },
Z = new Length { Unit = Z.Unit },
};
}
}
public enum EntryType
{
Invalid,
Rotation,
Scale,
Translate,
Skew,
Matrix,
Perspective,
}
}
}