Code/VividPanel.cs
using Sandbox;
using Sandbox.UI;
using System;
using System.Collections.Generic;

namespace VividPanels;

public class VividPanelSystem : GameObjectSystem
{
	public VividPanelSystem( Scene scene ) : base( scene )
	{
		Listen( Stage.StartUpdate, 0, StartUpdate, "StartUpdate" );
	}

	void StartUpdate()
	{
		if ( !Game.IsPlaying || Scene.IsEditor )
			return;

		if ( !Scene.Camera.IsValid() )
			return;

		if ( !Scene.Camera.GetComponent<VividPanelRenderer>().IsValid() )
			Scene.Camera.AddComponent<VividPanelRenderer>();
	}
}

public class VividRootPanel : RootPanel
{
	internal Transform Transform;

	internal float MaxInteractionDistance;

	protected override void UpdateScale( Rect screenSize )
	{
		Scale = 2f;
	}

	protected override void UpdateBounds( Rect rect )
	{
		base.UpdateBounds( PanelBounds );
	}

	public override bool RayToLocalPosition( Ray ray, out Vector2 position, out float distance )
	{
		position = default( Vector2 );
		distance = 0f;
		Vector3? vector = new Plane( Transform.Position, Transform.Rotation.Forward ).Trace( in ray, twosided: false, MaxInteractionDistance );
		if ( !vector.HasValue )
		{
			return false;
		}
		distance = Vector3.DistanceBetween( vector.Value, ray.Position );
		if ( distance < 1f )
		{
			return false;
		}
		Vector3 vector2 = Transform.PointToLocal( vector.Value );
		Vector2 vector3 = new Vector2( vector2.y, 0f - vector2.z );
		vector3 *= 20f;
		if ( !IsInside( vector3 ) )
		{
			return false;
		}
		position = vector3;
		return true;
	}
}

[Title( "Vivid Panel" )]
[Category( "UI" )]
[Icon( "panorama_horizontal" )]
[EditorHandle( "materials/gizmo/ui.png" )]
public class VividPanel : Component
{
	public enum HAlignment
	{
		[Icon( "align_horizontal_left" )]
		Left = 1,
		[Icon( "align_horizontal_center" )]
		Center,
		[Icon( "align_horizontal_right" )]
		Right
	}

	public enum VAlignment
	{
		[Icon( "align_vertical_top" )]
		Top = 1,
		[Icon( "align_vertical_center" )]
		Center,
		[Icon( "align_vertical_bottom" )]
		Bottom
	}

	internal float WorldRenderScale = 1f;

	[Property, Change( "CreateVertexBuffer" )] public float RenderScale { get; set; } = 1f;
	[Property] public bool RenderBackFace { get; set; } = true;
	[Property, Change( "CreateVertexBuffer" )] public bool LookAtCamera { get; set; } = false;
	[Property, ShowIf( "LookAtCamera", true ), Change( "CreateVertexBuffer" )] public bool ConsistentSize { get; set; } = false;
	[Property, Change( "CreateVertexBuffer" )] public Vector2Int PanelSize { get; set; } = 512;
	[Property, Change( "CreateVertexBuffer" )] public HAlignment HorizontalAlign { get; set; } = HAlignment.Center;
	[Property, Change( "CreateVertexBuffer" )] public VAlignment VerticalAlign { get; set; } = VAlignment.Center;
	[Property, Change( "CreateVertexBuffer" )] public float InteractionRange { get; set; } = 1000f;
	[Property] public float RenderDistance { get; set; } = 0f;

	public VividRootPanel RootPanel;
	public PanelComponent Source;

	public Texture Texture;
	public int VertexCount;
	public GpuBuffer<Vertex> VertexBuffer;

	protected override void OnStart()
	{
		base.OnStart();

		RootPanel = new VividRootPanel
		{
			RenderedManually = true,
			Scene = Scene,
			IsWorldPanel = true,
		};

		Source = GetComponent<PanelComponent>();

		CreateVertexBuffer();
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		if ( LookAtCamera )
		{
			WorldRotation = Rotation.LookAt( Scene.Camera.WorldRotation.Backward, Vector3.Up );
		}

		if ( !RootPanel.IsValid() )
			return;

		if ( !RootPanel.RenderedManually )
		{
			RootPanel.RenderedManually = true;
		}

		if (
			Source.IsValid()
			&& Source.Panel.IsValid()
			&& Source.Panel.Parent != RootPanel
		)
		{
			Source.Panel.Parent = RootPanel;
		}

		if ( Source.IsValid() && Source.Panel.IsValid() )
		{
			if ( Source.Panel.Parent != RootPanel )
			{
				Source.Panel.Parent = RootPanel;
			}
		}
	}

	protected override void OnDestroy()
	{
		base.OnDestroy();

		RootPanel?.Delete();
		RootPanel = null;
	}

	public Rect CalculateRect()
	{
		Rect result = new Rect( (Vector2)0.0, PanelSize );
		if ( HorizontalAlign == HAlignment.Center )
		{
			result.Position -= new Vector2( PanelSize.x * 0.5f, 0f );
		}
		if ( HorizontalAlign == HAlignment.Right )
		{
			result.Position -= new Vector2( PanelSize.x, 0f );
		}
		if ( VerticalAlign == VAlignment.Center )
		{
			result.Position -= new Vector2( 0f, PanelSize.y * 0.5f );
		}
		if ( VerticalAlign == VAlignment.Bottom )
		{
			result.Position -= new Vector2( 0f, PanelSize.y );
		}

		var camera = Scene.Camera;

		var scale = 1f;
		WorldRenderScale = RenderScale;
		if ( camera.IsValid() && Game.IsPlaying && LookAtCamera && ConsistentSize )
		{
			float depth = Vector3.Dot( WorldPosition - Scene.Camera.WorldPosition, Scene.Camera.WorldRotation.Forward );
			if ( depth > 0 )
			{
				scale = depth / 100f;

				WorldRenderScale = RenderScale * scale;
				result *= scale;
			}
		}

		return result;
	}

	protected override void DrawGizmos()
	{
		using ( Gizmo.Scope( null, new Transform( 0f, Rotation.From( 0f, 90f, -90f ), Sandbox.UI.WorldPanel.ScreenToWorldScale ) ) )
		{
			Rect rect = CalculateRect();
			Gizmo.Draw.Line( (Vector3)rect.TopLeft, (Vector3)rect.TopRight );
			Gizmo.Draw.Line( (Vector3)rect.TopLeft, (Vector3)rect.BottomLeft );
			Gizmo.Draw.Line( (Vector3)rect.TopRight, (Vector3)rect.BottomRight );
			Gizmo.Draw.Line( (Vector3)rect.BottomLeft, (Vector3)rect.BottomRight );
			Gizmo.Draw.Color = Color.Cyan.WithAlpha( 0.2f );
			Gizmo.Draw.SolidTriangle( new Triangle( rect.TopLeft, rect.TopRight, rect.BottomRight ) );
			Gizmo.Draw.SolidTriangle( new Triangle( rect.BottomRight, rect.BottomLeft, rect.TopLeft ) );
		}
	}

	void CreateVertexBuffer()
	{
		var rotation = WorldRotation * Rotation.From( 0f, 90f, -90f );
		var position = WorldPosition;

		var scale = Sandbox.UI.WorldPanel.ScreenToWorldScale;
		float dist = Vector3.DistanceBetween( Scene.Camera.WorldPosition, WorldPosition );

		Rect rect = CalculateRect();
		List<Vertex> vertices =
		[
			new Vertex( position + rotation * ((Vector3)rect.TopLeft * scale), rotation.Forward, rotation.Right, new Vector4( 0, 0, 0, 0 ) ),
			new Vertex( position + rotation * ((Vector3)rect.BottomLeft * scale), rotation.Forward, rotation.Right, new Vector4( 0, 1, 0, 0 ) ),
			new Vertex( position + rotation * ((Vector3)rect.BottomRight * scale), rotation.Forward, rotation.Right, new Vector4( 1, 1, 0, 0 ) ),

			new Vertex( position + rotation * ((Vector3)rect.BottomRight * scale), rotation.Forward, rotation.Right, new Vector4( 1, 1, 0, 0 ) ),
			new Vertex( position + rotation * ((Vector3)rect.TopRight * scale), rotation.Forward, rotation.Right, new Vector4( 1, 0, 0, 0 ) ),
			new Vertex( position + rotation * ((Vector3)rect.TopLeft * scale), rotation.Forward, rotation.Right, new Vector4( 0, 0, 0, 0 ) ),
		];

		VertexCount = vertices.Count;
		VertexBuffer = new GpuBuffer<Vertex>( VertexCount, GpuBuffer.UsageFlags.Vertex );
		VertexBuffer.SetData( vertices );

		RootPanel.Transform = Transform.World.WithScale( WorldScale * WorldRenderScale );
		RootPanel.MaxInteractionDistance = InteractionRange;
	}
}