VrWorldInput.cs

Component that visualizes VR world input. On each update it gets the hovered UI panel from WorldInput, computes a 3D hit point from a world-space ray against the panel or its root, then draws a cyan line and a sphere at the hit point.

Networking
using Sandbox;

public sealed class VrWorldInput : Component
{
	[RequireComponent] private WorldInput WorldInput { get; set; }

	private const float HitSphereRadius = 0.5f;

	protected override void OnUpdate()
	{
		var hovered = WorldInput.Hovered;
		if ( !hovered.IsValid() )
			return;

		var origin = WorldPosition;
		var ray = new Ray( origin, WorldRotation.Forward );

		if ( !TryGetHitPoint( hovered, ray, out var hitPoint ) )
			return;

		Gizmo.Draw.Color = Color.Cyan;
		Gizmo.Draw.Line( origin, hitPoint );
		Gizmo.Draw.SolidSphere( hitPoint, HitSphereRadius );
	}

	/// <summary>
	/// Nested hovered panels have no 3D transform; hit-test the root, then fall back to the panel plane.
	/// </summary>
	private static bool TryGetHitPoint( Sandbox.UI.Panel panel, Ray ray, out Vector3 hitPoint )
	{
		hitPoint = default;

		var root = panel.FindRootPanel();
		if ( root.IsValid() && root.RayToLocalPosition( ray, out _, out var distance ) && distance > 0f )
		{
			hitPoint = ray.Position + ray.Forward * distance;
			return true;
		}

		var go = panel.GameObject;
		if ( !go.IsValid() )
			return false;

		// LookAtCamera panels face the viewer; use a plane through the panel toward the ray origin.
		var planePos = go.WorldPosition;
		var planeNormal = (ray.Position - planePos).Normal;
		var denom = Vector3.Dot( ray.Forward, planeNormal );
		if ( denom.AlmostEqual( 0f ) )
			return false;

		var t = Vector3.Dot( planePos - ray.Position, planeNormal ) / denom;
		if ( t < 0f )
			return false;

		hitPoint = ray.Position + ray.Forward * t;
		return true;
	}
}