Files
sbox-public/engine/Sandbox.Engine/Systems/UI/WorldPanel/WorldPanel.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

164 lines
4.2 KiB
C#

namespace Sandbox.UI
{
/// <summary>
/// An interactive 2D panel rendered in the 3D world.
/// </summary>
public class WorldPanel : RootPanel
{
public static float ScreenToWorldScale => ScenePanelObject.ScreenToWorldScale;
/// <summary>
/// Scene object that renders the panel.
/// </summary>
internal ScenePanelObject SceneObject { get; set; }
/// <summary>
/// Transform of the world panel in 3D space.
/// </summary>
public Transform Transform
{
get => SceneObject.Transform;
set => SceneObject.Transform = value;
}
/// <summary>
/// Tags that are applied to the underlying SceneObject
/// </summary>
public ITagSet Tags => SceneObject.Tags;
/// <summary>
/// Position of the world panel in 3D space.
/// </summary>
public Vector3 Position
{
get => Transform.Position;
set => Transform = Transform.WithPosition( value );
}
/// <summary>
/// Rotation of the world panel in 3D space.
/// </summary>
public Rotation Rotation
{
get => Transform.Rotation;
set => Transform = Transform.WithRotation( value );
}
/// <summary>
/// Scale of the world panel in 3D space.
/// </summary>
public float WorldScale
{
get => Transform.UniformScale;
set => Transform = Transform.WithScale( value );
}
/// <summary>
/// Maximum distance at which a player can interact with this world panel.
/// </summary>
public float MaxInteractionDistance { get; set; }
public WorldPanel( SceneWorld world )
{
ArgumentNullException.ThrowIfNull( world, "world" );
SceneObject = new ScenePanelObject( world, this );
SceneObject.Flags.IsOpaque = false;
SceneObject.Flags.IsTranslucent = true;
// Don't render this panel using the panel system
RenderedManually = true;
// Default size is 1000x1000, centered on scene object transform
PanelBounds = new Rect( -500, -500, 1000, 1000 );
// World panels are scaled down to world units,
// so boost the panel scale to sensible default
Scale = 2.0f;
MaxInteractionDistance = 1000.0f;
// This is a world panel - we need to set this so that layers
// get the attribute restored properly
IsWorldPanel = true;
}
/// <summary>
/// Update the bounds for this panel. We purposely do nothing here because
/// on world panels you can change the bounds by setting <see cref="RootPanel.PanelBounds"/>.
/// </summary>
protected override void UpdateBounds( Rect rect )
{
if ( !SceneObject.IsValid() )
return;
var right = Rotation.Right;
var down = Rotation.Down;
var panelBounds = PanelBounds * WorldScale * ScenePanelObject.ScreenToWorldScale;
//
// Work out the bounds by adding each corner to a bbox
//
var bounds = BBox.FromPositionAndSize( right * panelBounds.Left + down * panelBounds.Top );
bounds = bounds.AddPoint( right * panelBounds.Left + down * panelBounds.Bottom );
bounds = bounds.AddPoint( right * panelBounds.Right + down * panelBounds.Top );
bounds = bounds.AddPoint( right * panelBounds.Right + down * panelBounds.Bottom );
SceneObject.Bounds = bounds + Position;
}
/// <summary>
/// We override this to prevent the scale automatically being set based on screen
/// size changing.. because that's obviously not needed here.
/// </summary>
protected override void UpdateScale( Rect screenSize )
{
}
public override void Delete( bool immediate = false )
{
base.Delete( immediate );
}
public override void OnDeleted()
{
base.OnDeleted();
SceneObject?.Delete();
SceneObject = null;
}
public override bool RayToLocalPosition( Ray ray, out Vector2 position, out float distance )
{
position = default;
distance = 0;
var plane = new Plane( Position, Rotation.Forward );
var pos = plane.Trace( ray, false, MaxInteractionDistance );
if ( !pos.HasValue )
return false;
distance = Vector3.DistanceBetween( pos.Value, ray.Position );
if ( distance < 1 )
return false;
// to local coords
var localPos3 = Transform.PointToLocal( pos.Value );
var localPos = new Vector2( localPos3.y, -localPos3.z );
// convert to screen coords
localPos *= 1f / ScenePanelObject.ScreenToWorldScale;
if ( !IsInside( localPos ) )
return false;
position = localPos;
return true;
}
}
}