refactor(render): extract frame presentation diagnostics
This commit is contained in:
parent
7e4cfb37c3
commit
733126a272
20 changed files with 3767 additions and 1021 deletions
309
src/AcDream.App/Rendering/PaperdollFramePresenter.cs
Normal file
309
src/AcDream.App/Rendering/PaperdollFramePresenter.cs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Input;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
internal interface IPaperdollDollRenderer
|
||||
{
|
||||
void SetDoll(WorldEntity? doll);
|
||||
|
||||
uint Render(int width, int height);
|
||||
}
|
||||
|
||||
internal interface IPaperdollFrameView
|
||||
{
|
||||
bool TryGetVisibleSize(out int width, out int height);
|
||||
|
||||
void SetTextureHandle(uint textureHandle);
|
||||
}
|
||||
|
||||
internal interface IPaperdollInventoryVisibility
|
||||
{
|
||||
bool IsVisible { get; }
|
||||
}
|
||||
|
||||
internal interface IPaperdollDollFactory
|
||||
{
|
||||
bool TryBuild(out WorldEntity? doll);
|
||||
}
|
||||
|
||||
internal interface IPaperdollEntityLookup
|
||||
{
|
||||
bool TryGet(uint serverGuid, out WorldEntity player);
|
||||
}
|
||||
|
||||
internal interface IPaperdollPoseApplicator
|
||||
{
|
||||
void Apply(WorldEntity doll, uint setupId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owns paperdoll dirty/rebuild state and the private FBO presentation edge.
|
||||
/// The GL renderer remains a borrowed resource disposed by the existing window
|
||||
/// shutdown transaction.
|
||||
/// </summary>
|
||||
internal sealed class PaperdollFramePresenter
|
||||
{
|
||||
private readonly IPaperdollDollRenderer _renderer;
|
||||
private readonly IPaperdollFrameView _view;
|
||||
private readonly IPaperdollDollFactory _factory;
|
||||
private bool _dirty = true;
|
||||
|
||||
public PaperdollFramePresenter(
|
||||
IPaperdollDollRenderer renderer,
|
||||
IPaperdollFrameView view,
|
||||
IPaperdollDollFactory factory)
|
||||
{
|
||||
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
_view = view ?? throw new ArgumentNullException(nameof(view));
|
||||
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
internal bool IsDirty => _dirty;
|
||||
|
||||
public void MarkDirty() => _dirty = true;
|
||||
|
||||
public void Render()
|
||||
{
|
||||
if (!_view.TryGetVisibleSize(out int width, out int height))
|
||||
return;
|
||||
|
||||
if (_dirty)
|
||||
{
|
||||
if (_factory.TryBuild(out WorldEntity? doll))
|
||||
{
|
||||
_renderer.SetDoll(doll);
|
||||
_dirty = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retail's private viewport remains present while the live
|
||||
// player projection is still materializing. Keep the dirty
|
||||
// edge armed and clear any doll from the prior session.
|
||||
_renderer.SetDoll(null);
|
||||
}
|
||||
}
|
||||
|
||||
_view.SetTextureHandle(_renderer.Render(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Retained-UI visibility and texture publication for the doll view.</summary>
|
||||
internal sealed class RetailPaperdollFrameView : IPaperdollFrameView
|
||||
{
|
||||
private readonly UiViewport _viewport;
|
||||
private readonly IPaperdollInventoryVisibility _inventory;
|
||||
|
||||
public RetailPaperdollFrameView(
|
||||
UiViewport viewport,
|
||||
IPaperdollInventoryVisibility inventory)
|
||||
{
|
||||
_viewport = viewport ?? throw new ArgumentNullException(nameof(viewport));
|
||||
_inventory = inventory ?? throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
public bool TryGetVisibleSize(out int width, out int height)
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
if (!_viewport.Visible || !_inventory.IsVisible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
width = (int)_viewport.Width;
|
||||
height = (int)_viewport.Height;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetTextureHandle(uint textureHandle) =>
|
||||
_viewport.TextureHandle = textureHandle;
|
||||
}
|
||||
|
||||
/// <summary>Narrow visibility adapter for the paperdoll's inventory host.</summary>
|
||||
internal sealed class PaperdollInventoryVisibility : IPaperdollInventoryVisibility
|
||||
{
|
||||
private readonly UiElement _inventoryFrame;
|
||||
|
||||
public PaperdollInventoryVisibility(UiElement inventoryFrame)
|
||||
{
|
||||
_inventoryFrame = inventoryFrame
|
||||
?? throw new ArgumentNullException(nameof(inventoryFrame));
|
||||
}
|
||||
|
||||
public bool IsVisible => _inventoryFrame.Visible;
|
||||
}
|
||||
|
||||
/// <summary>Canonical live-entity lookup used by the paperdoll factory.</summary>
|
||||
internal sealed class LivePaperdollEntityLookup : IPaperdollEntityLookup
|
||||
{
|
||||
private readonly LiveEntityRuntime _liveEntities;
|
||||
|
||||
public LivePaperdollEntityLookup(LiveEntityRuntime liveEntities)
|
||||
{
|
||||
_liveEntities = liveEntities
|
||||
?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
}
|
||||
|
||||
public bool TryGet(uint serverGuid, out WorldEntity player) =>
|
||||
_liveEntities.TryGetWorldEntity(serverGuid, out player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the static retail paperdoll clone from the canonical live player
|
||||
/// projection and applies the DAT-defined held pose.
|
||||
/// </summary>
|
||||
internal sealed class RetailPaperdollDollFactory : IPaperdollDollFactory
|
||||
{
|
||||
private readonly IPaperdollEntityLookup _entities;
|
||||
private readonly ILocalPlayerIdentitySource _identity;
|
||||
private readonly IPaperdollPoseApplicator _pose;
|
||||
|
||||
public RetailPaperdollDollFactory(
|
||||
IPaperdollEntityLookup entities,
|
||||
ILocalPlayerIdentitySource identity,
|
||||
IPaperdollPoseApplicator pose)
|
||||
{
|
||||
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
||||
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
|
||||
_pose = pose ?? throw new ArgumentNullException(nameof(pose));
|
||||
}
|
||||
|
||||
public bool TryBuild(out WorldEntity? doll)
|
||||
{
|
||||
doll = null;
|
||||
if (!_entities.TryGet(_identity.ServerGuid, out WorldEntity player)
|
||||
|| player.MeshRefs.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint? basePalette = null;
|
||||
List<(uint, byte, byte)>? subPalettes = null;
|
||||
if (player.PaletteOverride is { } palette)
|
||||
{
|
||||
basePalette = palette.BasePaletteId;
|
||||
subPalettes = new List<(uint, byte, byte)>();
|
||||
foreach (var range in palette.SubPalettes)
|
||||
{
|
||||
subPalettes.Add((
|
||||
range.SubPaletteId,
|
||||
range.Offset,
|
||||
range.Length));
|
||||
}
|
||||
}
|
||||
|
||||
List<(byte, uint)>? partOverrides = null;
|
||||
if (player.PartOverrides.Count > 0)
|
||||
{
|
||||
partOverrides = new List<(byte, uint)>(player.PartOverrides.Count);
|
||||
foreach (var part in player.PartOverrides)
|
||||
partOverrides.Add((part.PartIndex, part.GfxObjId));
|
||||
}
|
||||
|
||||
doll = DollEntityBuilder.Build(
|
||||
player.SourceGfxObjOrSetupId,
|
||||
new List<MeshRef>(player.MeshRefs),
|
||||
basePalette,
|
||||
subPalettes,
|
||||
partOverrides);
|
||||
_pose.Apply(doll, player.SourceGfxObjOrSetupId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies retail's DAT-defined settled paperdoll stance.</summary>
|
||||
internal sealed class RetailPaperdollPoseApplicator : IPaperdollPoseApplicator
|
||||
{
|
||||
private readonly IDatReaderWriter _dats;
|
||||
private readonly IAnimationLoader _animations;
|
||||
private readonly object _datLock;
|
||||
|
||||
public RetailPaperdollPoseApplicator(
|
||||
IDatReaderWriter dats,
|
||||
IAnimationLoader animations,
|
||||
object datLock)
|
||||
{
|
||||
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
|
||||
_animations = animations ?? throw new ArgumentNullException(nameof(animations));
|
||||
_datLock = datLock ?? throw new ArgumentNullException(nameof(datLock));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>gmPaperDollUI</c> resolves its held pose with
|
||||
/// <c>DBCache::GetDIDFromEnumStatic(0x10000005, 7)</c>. The master map
|
||||
/// therefore resolves key 7 to a sub-map, then key 0x10000005 to the
|
||||
/// Animation DID.
|
||||
/// </summary>
|
||||
private uint ResolvePoseDid()
|
||||
{
|
||||
uint masterDid = (uint)_dats.Portal.Db.Header.MasterMapId;
|
||||
if (masterDid == 0
|
||||
|| !_dats.Portal.TryGet<DatReaderWriter.DBObjs.EnumIDMap>(
|
||||
masterDid,
|
||||
out var master)
|
||||
|| !master.ClientEnumToID.TryGetValue(7u, out uint subDid)
|
||||
|| !_dats.Portal.TryGet<DatReaderWriter.DBObjs.EnumIDMap>(
|
||||
subDid,
|
||||
out var sub))
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return sub.ClientEnumToID.TryGetValue(0x10000005u, out uint did)
|
||||
? did
|
||||
: 0u;
|
||||
}
|
||||
|
||||
public void Apply(WorldEntity doll, uint setupId)
|
||||
{
|
||||
DatReaderWriter.DBObjs.Animation? animation;
|
||||
DatReaderWriter.DBObjs.Setup? setup;
|
||||
lock (_datLock)
|
||||
{
|
||||
uint poseDid = ResolvePoseDid();
|
||||
if ((poseDid >> 24) != 0x03u)
|
||||
return;
|
||||
|
||||
animation = _animations.LoadAnimation(poseDid);
|
||||
setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(setupId);
|
||||
}
|
||||
if (animation is null || setup is null || animation.PartFrames.Count == 0)
|
||||
return;
|
||||
|
||||
// RedressCreature @ 0x004A3C22 installs the pose with zero frame rate
|
||||
// and holds the settled final frame.
|
||||
var frame = animation.PartFrames[^1];
|
||||
var reposed = new List<MeshRef>(doll.MeshRefs.Count);
|
||||
for (int index = 0; index < doll.MeshRefs.Count; index++)
|
||||
{
|
||||
Vector3 scale = index < setup.DefaultScale.Count
|
||||
? setup.DefaultScale[index]
|
||||
: Vector3.One;
|
||||
Vector3 origin = Vector3.Zero;
|
||||
Quaternion orientation = Quaternion.Identity;
|
||||
if (index < frame.Frames.Count)
|
||||
{
|
||||
origin = frame.Frames[index].Origin;
|
||||
orientation = frame.Frames[index].Orientation;
|
||||
}
|
||||
|
||||
Matrix4x4 transform = Matrix4x4.CreateScale(scale)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(origin);
|
||||
MeshRef source = doll.MeshRefs[index];
|
||||
reposed.Add(new MeshRef(source.GfxObjId, transform)
|
||||
{
|
||||
SurfaceOverrides = source.SurfaceOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
doll.MeshRefs = reposed;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue