feat(ui): port retail creature appraisal presentation
Render assessed creatures through the shared private viewport with retail heading, bounding-box camera, and light. Build the exact authored nine-row stat list and resolve creature names from the retail EnumMapper while keeping remaining font/sequencer adaptations explicit. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
f1a7912160
commit
7eaa68a5f4
26 changed files with 2780 additions and 237 deletions
252
src/AcDream.App/Rendering/PrivateEntityViewportRenderer.cs
Normal file
252
src/AcDream.App/Rendering/PrivateEntityViewportRenderer.cs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.World;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Camera contract for retail <c>CreatureMode</c>-style UI viewports. The
|
||||
/// renderer supplies the authored viewport aspect before each draw and uses
|
||||
/// <see cref="Eye"/> when it publishes the private scene lighting UBO.
|
||||
/// </summary>
|
||||
internal interface IPrivateEntityViewportCamera : ICamera
|
||||
{
|
||||
Vector3 Eye { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared render-to-texture implementation for the private 3-D creature
|
||||
/// viewports used by paperdoll and examination UI. Each instance owns one FBO,
|
||||
/// one synthetic render identity, and one balanced texture-owner lease.
|
||||
/// </summary>
|
||||
internal sealed unsafe class PrivateEntityViewportRenderer :
|
||||
IUiViewportRenderer,
|
||||
IDisposable
|
||||
{
|
||||
private const uint PrivateLandblockId = 0u;
|
||||
|
||||
private readonly GL _gl;
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly SceneLightingUboBinding _lightUbo;
|
||||
private readonly FixedEntityTextureOwnerLease _textureOwnerLease;
|
||||
private readonly IPrivateEntityViewportCamera _camera;
|
||||
private readonly HashSet<uint> _animatedIds;
|
||||
private readonly string _diagnosticName;
|
||||
|
||||
private uint _fbo;
|
||||
private uint _colorTex;
|
||||
private uint _depthRbo;
|
||||
private int _fbW;
|
||||
private int _fbH;
|
||||
private WorldEntity? _entity;
|
||||
|
||||
public PrivateEntityViewportRenderer(
|
||||
GL gl,
|
||||
WbDrawDispatcher dispatcher,
|
||||
SceneLightingUboBinding lightUbo,
|
||||
IEntityTextureLifetime textureLifetime,
|
||||
uint renderId,
|
||||
IPrivateEntityViewportCamera camera,
|
||||
string diagnosticName)
|
||||
{
|
||||
if (renderId == 0u)
|
||||
throw new ArgumentOutOfRangeException(nameof(renderId));
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
_lightUbo = lightUbo ?? throw new ArgumentNullException(nameof(lightUbo));
|
||||
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
|
||||
_diagnosticName = string.IsNullOrWhiteSpace(diagnosticName)
|
||||
? "creature viewport"
|
||||
: diagnosticName;
|
||||
_animatedIds = [renderId];
|
||||
_textureOwnerLease = new FixedEntityTextureOwnerLease(
|
||||
textureLifetime ?? throw new ArgumentNullException(nameof(textureLifetime)),
|
||||
renderId);
|
||||
}
|
||||
|
||||
public void SetEntity(WorldEntity? entity)
|
||||
{
|
||||
if (ReferenceEquals(_entity, entity))
|
||||
return;
|
||||
_textureOwnerLease.Replace(entity is not null);
|
||||
_entity = entity;
|
||||
}
|
||||
|
||||
public uint Render(int width, int height)
|
||||
{
|
||||
WorldEntity? entity = _entity;
|
||||
if (entity is null || entity.MeshRefs.Count == 0 || width <= 0 || height <= 0)
|
||||
return 0u;
|
||||
|
||||
EnsureFramebuffer(width, height);
|
||||
if (_fbo == 0u)
|
||||
return 0u;
|
||||
_camera.Aspect = width / (float)height;
|
||||
|
||||
using var scope = new GLStateScope(_gl);
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo);
|
||||
_gl.Viewport(0, 0, (uint)width, (uint)height);
|
||||
_gl.Disable(EnableCap.ScissorTest);
|
||||
_gl.ClearColor(0f, 0f, 0f, 0f);
|
||||
_gl.ClearDepth(1.0);
|
||||
_gl.DepthMask(true);
|
||||
_gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
_gl.DepthFunc(DepthFunction.Less);
|
||||
_gl.Enable(EnableCap.CullFace);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
_gl.FrontFace(FrontFaceDirection.Ccw);
|
||||
_gl.Disable(EnableCap.Blend);
|
||||
|
||||
UploadCreatureLight();
|
||||
|
||||
WorldEntity[] entities = [entity];
|
||||
var entries =
|
||||
new (uint, Vector3, Vector3, IReadOnlyList<WorldEntity>,
|
||||
IReadOnlyDictionary<uint, WorldEntity>?)[]
|
||||
{
|
||||
(
|
||||
PrivateLandblockId,
|
||||
new Vector3(-1024f),
|
||||
new Vector3(1024f),
|
||||
entities,
|
||||
null),
|
||||
};
|
||||
|
||||
_dispatcher.Draw(
|
||||
_camera,
|
||||
entries,
|
||||
frustum: null,
|
||||
neverCullLandblockId: PrivateLandblockId,
|
||||
visibleCellIds: null,
|
||||
animatedEntityIds: _animatedIds);
|
||||
return _colorTex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Both retail paperdoll and creature examination call
|
||||
/// <c>UIElement_Viewport::SetLight(DISTANT_LIGHT, 2, (0.3,1.9,0.65))</c>.
|
||||
/// </summary>
|
||||
private void UploadCreatureLight()
|
||||
{
|
||||
Vector3 direction = Vector3.Normalize(new Vector3(0.3f, 1.9f, 0.65f));
|
||||
_lightUbo.Upload(new SceneLightingUbo
|
||||
{
|
||||
Light0 = new UboLight
|
||||
{
|
||||
PosAndKind = Vector4.Zero,
|
||||
DirAndRange = new Vector4(direction, 1e9f),
|
||||
ColorAndIntensity = new Vector4(1f, 1f, 1f, 2f),
|
||||
ConeAngleEtc = Vector4.Zero,
|
||||
},
|
||||
CellAmbient = new Vector4(0.3f, 0.3f, 0.3f, 1f),
|
||||
FogParams = new Vector4(1e9f, 1e9f, 0f, 0f),
|
||||
FogColor = Vector4.Zero,
|
||||
CameraAndTime = new Vector4(_camera.Eye, 0f),
|
||||
});
|
||||
}
|
||||
|
||||
private void EnsureFramebuffer(int width, int height)
|
||||
{
|
||||
if (_fbo != 0u && width == _fbW && height == _fbH)
|
||||
return;
|
||||
DeleteFramebuffer();
|
||||
|
||||
_fbW = width;
|
||||
_fbH = height;
|
||||
_colorTex = _gl.GenTexture();
|
||||
_gl.BindTexture(TextureTarget.Texture2D, _colorTex);
|
||||
_gl.TexImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
InternalFormat.Rgba8,
|
||||
(uint)width,
|
||||
(uint)height,
|
||||
0,
|
||||
PixelFormat.Rgba,
|
||||
PixelType.UnsignedByte,
|
||||
(void*)0);
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureMinFilter,
|
||||
(int)TextureMinFilter.Linear);
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureMagFilter,
|
||||
(int)TextureMinFilter.Linear);
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureWrapS,
|
||||
(int)TextureWrapMode.ClampToEdge);
|
||||
_gl.TexParameter(
|
||||
TextureTarget.Texture2D,
|
||||
TextureParameterName.TextureWrapT,
|
||||
(int)TextureWrapMode.ClampToEdge);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, 0u);
|
||||
|
||||
_depthRbo = _gl.GenRenderbuffer();
|
||||
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _depthRbo);
|
||||
_gl.RenderbufferStorage(
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
InternalFormat.Depth24Stencil8,
|
||||
(uint)width,
|
||||
(uint)height);
|
||||
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0u);
|
||||
|
||||
_fbo = _gl.GenFramebuffer();
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _fbo);
|
||||
_gl.FramebufferTexture2D(
|
||||
FramebufferTarget.Framebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
TextureTarget.Texture2D,
|
||||
_colorTex,
|
||||
0);
|
||||
_gl.FramebufferRenderbuffer(
|
||||
FramebufferTarget.Framebuffer,
|
||||
FramebufferAttachment.DepthStencilAttachment,
|
||||
RenderbufferTarget.Renderbuffer,
|
||||
_depthRbo);
|
||||
|
||||
GLEnum status = _gl.CheckFramebufferStatus(
|
||||
FramebufferTarget.Framebuffer);
|
||||
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0u);
|
||||
if (status != GLEnum.FramebufferComplete)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[{_diagnosticName}] framebuffer incomplete: {status} ({width}x{height})");
|
||||
DeleteFramebuffer();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteFramebuffer()
|
||||
{
|
||||
if (_fbo != 0u)
|
||||
{
|
||||
_gl.DeleteFramebuffer(_fbo);
|
||||
_fbo = 0u;
|
||||
}
|
||||
if (_colorTex != 0u)
|
||||
{
|
||||
_gl.DeleteTexture(_colorTex);
|
||||
_colorTex = 0u;
|
||||
}
|
||||
if (_depthRbo != 0u)
|
||||
{
|
||||
_gl.DeleteRenderbuffer(_depthRbo);
|
||||
_depthRbo = 0u;
|
||||
}
|
||||
_fbW = 0;
|
||||
_fbH = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_entity = null;
|
||||
_textureOwnerLease.Dispose();
|
||||
DeleteFramebuffer();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue