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 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; } /// /// Returns true if this is empty. /// 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(); 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(); 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(); 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(); List = List.Add( new Entry { Type = EntryType.Translate, Z = length.Value } ); return true; } public bool AddScale( float scale ) { if ( List == null ) List = ImmutableList.Create(); 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(); 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(); 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(); 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(); List = List.Add( new Entry { Type = EntryType.Matrix, Matrix = matrix } ); return true; } public bool AddPerspective( Length d ) { if ( List == null ) List = ImmutableList.Create(); 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(); 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(); 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>.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, } } }