UI panel that renders a LootPrefab's 3D model into a ScenePanel for use as a live loot icon. It builds a small SceneWorld with lights and a SceneObject using the prefab's model, IconOffset and IconAngles, and configures the ScenePanel camera and layout.
using System;
using Sandbox;
using Sandbox.UI;
namespace BrickJam.UI;
/// <summary>
/// Renders a loot prefab's 3D model live in the UI. The legacy pre-rendered icon texture relied on
/// <c>Graphics.RenderToTexture( SceneCamera, Texture )</c>, which is now a no-op stub in the engine (it just
/// returns false), so loot icons rendered blank. Instead we build a tiny <see cref="SceneWorld"/> with the
/// model + lights and show it through a <see cref="ScenePanel"/> - the same proven approach as
/// <c>Lockpicker.razor</c>. The model is framed using the prefab's authored <c>IconOffset</c>/<c>IconAngles</c>.
/// </summary>
public class LootIcon : Panel
{
private LootPrefab prefab;
private ScenePanel scenePanel;
private SceneWorld world;
public LootPrefab Prefab
{
get => prefab;
set
{
if ( prefab == value )
return;
prefab = value;
Rebuild();
}
}
private void Rebuild()
{
Teardown();
if ( prefab is null || string.IsNullOrWhiteSpace( prefab.Model ) )
return;
var model = Model.Load( prefab.Model );
if ( model is null || model.IsError )
return;
world = new SceneWorld { AmbientLightColor = Color.White * 0.35f };
_ = new ScenePointLight( world, Vector3.Up * 15f + Vector3.Backward * 5f, 200f, Color.White );
_ = new ScenePointLight( world, Vector3.Up * 25f + Vector3.Forward * 10f, 200f, Color.White );
_ = new ScenePointLight( world, Vector3.Backward * 5f + Vector3.Down * 35f, 200f, Color.White * 0.5f );
_ = new SceneObject( world, model, new Transform( prefab.IconOffset, prefab.IconAngles.ToRotation() ) );
scenePanel = AddChild<ScenePanel>( "scene" );
scenePanel.World = world;
scenePanel.Camera.Position = Vector3.Backward * 10f;
scenePanel.Camera.Rotation = Rotation.Identity;
scenePanel.Camera.FieldOfView = 50f;
scenePanel.Camera.ZNear = 5f;
scenePanel.Camera.ZFar = 1000f;
scenePanel.Camera.BackgroundColor = Color.Transparent;
// Fill the host cell exactly (the parent .item clips with overflow:hidden) so the live render can't
// spill across the grid.
scenePanel.Style.Position = PositionMode.Absolute;
scenePanel.Style.Left = Length.Percent( 0 );
scenePanel.Style.Top = Length.Percent( 0 );
scenePanel.Style.Width = Length.Percent( 100 );
scenePanel.Style.Height = Length.Percent( 100 );
}
private void Teardown()
{
scenePanel?.Delete();
scenePanel = null;
world?.Delete();
world = null;
}
public override void OnDeleted()
{
base.OnDeleted();
Teardown();
}
}