fix(D.2b): Slice 2 visual gate — frame the whole doll + caption the Slots toggle
Visual gate 1 (user): the doll rendered but only the legs showed (camera aimed at the model origin = the feet) and the Slots button was invisible. - DollCamera: aim the look-at at mid-body (~0.95 m) and stand back ~3.7 m so the whole ~1.9 m figure fits. (Size is a later retail-comparison polish per the user.) - PaperdollController: the Slots button (0x100005BE) is found + wired (diagnostic confirmed armorSlots=9/9, viewport=UiViewport, slotsButton=UiButton) but its dat element has no face sprite, so it drew nothing. Give it a gold "Slots" caption (UiButton.Label, like chat Send) via a new datFont param on Bind. Temporary [Slice2-paperdoll] diagnostic logs the button rect + the found widgets (stripped at wrap-up). Idle animation deferred to a focused follow-up (faithful idle needs a full AnimatedEntity + Sequencer through the TickAnimations multi-branch path + re-dress coordination — real integration risk vs the verified static doll). Build + full App suite green (594). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b0365e7a9
commit
fe319bd2aa
4 changed files with 41 additions and 14 deletions
|
|
@ -3,21 +3,26 @@ using System.Numerics;
|
|||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed camera for the paperdoll mini-scene. Eye and look-at from
|
||||
/// <c>gmPaperDollUI::PostInit</c> (decomp lines 175521–175527):
|
||||
/// eye (0.12, -2.4, 0.88) looking at the origin, AC up-axis = +Z.
|
||||
/// Fixed camera for the paperdoll mini-scene. Derived from <c>gmPaperDollUI::PostInit</c>
|
||||
/// (decomp lines 175521–175527: eye offset ~(0.12, -2.4, 0.88) about the origin), but with the
|
||||
/// look-at raised to mid-body and the camera pulled back after the first visual gate: the doll's
|
||||
/// model origin is at the FEET (z=0), so aiming at the origin framed only the lower legs. We aim
|
||||
/// at ~0.95 m (a human's vertical centre) and stand back ~3.7 m so the whole ~1.9 m figure fits.
|
||||
/// AC up-axis = +Z.
|
||||
///
|
||||
/// Uses the same <see cref="Matrix4x4.CreateLookAt"/> /
|
||||
/// <see cref="Matrix4x4.CreatePerspectiveFieldOfView"/> convention as
|
||||
/// <see cref="ChaseCamera"/> so the doll's triangle winding and back-face
|
||||
/// culling match the world render pass.
|
||||
///
|
||||
/// Per-race camera distance (<c>UpdateForRace</c>) is deferred polish.
|
||||
/// Exact per-race camera framing (<c>UpdateForRace</c>) is deferred polish.
|
||||
/// </summary>
|
||||
public sealed class DollCamera : ICamera
|
||||
{
|
||||
private static readonly Vector3 Eye = new(0.12f, -2.4f, 0.88f);
|
||||
private static readonly Vector3 Target = Vector3.Zero;
|
||||
// Body centre height (metres) the camera aims at — model origin is at the feet (z=0).
|
||||
private const float BodyCentreZ = 0.95f;
|
||||
private static readonly Vector3 Eye = new(0.12f, -3.7f, BodyCentreZ);
|
||||
private static readonly Vector3 Target = new(0f, 0f, BodyCentreZ);
|
||||
private static readonly Vector3 Up = Vector3.UnitZ; // AC up-axis = +Z, same as ChaseCamera
|
||||
|
||||
/// <summary>Vertical field of view in radians. ~34° default; tune at the visual gate.</summary>
|
||||
|
|
|
|||
|
|
@ -2261,7 +2261,8 @@ public sealed class GameWindow : IDisposable
|
|||
sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask),
|
||||
// Empty equip slots show a visible frame (same square as the inventory grid) so every
|
||||
// slot position is seen + usable; the live 3D character (the doll) is Slice 2.
|
||||
emptySlotSprite: contentsEmpty);
|
||||
emptySlotSprite: contentsEmpty,
|
||||
datFont: vitalsDatFont); // Slice 2: caption the "Slots" toggle button
|
||||
|
||||
// Slice 2: capture the doll viewport widget (Type 0xD, element 0x100001D5) + the inventory
|
||||
// frame so the per-frame pre-UI hook can render the 3-D doll into the widget's texture only
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public sealed class PaperdollController : IItemListDragHandler
|
|||
private PaperdollController(
|
||||
ImportedLayout layout, ClientObjectTable objects, Func<uint> playerGuid,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds, Action<uint, uint>? sendWield,
|
||||
uint emptySlotSprite)
|
||||
uint emptySlotSprite, UiDatFont? datFont)
|
||||
{
|
||||
_objects = objects; _playerGuid = playerGuid; _iconIds = iconIds; _sendWield = sendWield;
|
||||
|
||||
|
|
@ -117,8 +117,27 @@ public sealed class PaperdollController : IItemListDragHandler
|
|||
|
||||
_dollViewport = layout.FindElement(0x100001D5u); // doll viewport (may be null until Slice 3)
|
||||
|
||||
if (layout.FindElement(0x100005BEu) is UiButton slotsBtn)
|
||||
var slotsBtnEl = layout.FindElement(0x100005BEu);
|
||||
if (slotsBtnEl is UiButton slotsBtn)
|
||||
{
|
||||
slotsBtn.OnClick = () => { _viewState.Toggle(); ApplyView(); };
|
||||
// The dat element has no face sprite, so without a label the button is invisible. Give it a
|
||||
// "Slots" caption (gold, like the chat Send button) so it's findable + clickable.
|
||||
if (datFont is not null)
|
||||
{
|
||||
slotsBtn.Label = "Slots";
|
||||
slotsBtn.LabelFont = datFont;
|
||||
slotsBtn.LabelColor = new System.Numerics.Vector4(1f, 0.92f, 0.72f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
// Slice 2 diagnostic (temporary): confirm the importer surfaced the toggle button + doll
|
||||
// viewport, and WHERE the button sits. Stripped once the toggle is visually confirmed.
|
||||
Console.WriteLine($"[Slice2-paperdoll] armorSlots={_armorSlots.Count}/9 " +
|
||||
$"viewport={_dollViewport?.GetType().Name ?? "MISSING"} " +
|
||||
$"slotsButton={slotsBtnEl?.GetType().Name ?? "MISSING"} " +
|
||||
$"btnRect=({slotsBtnEl?.Left ?? -1},{slotsBtnEl?.Top ?? -1},{slotsBtnEl?.Width ?? -1},{slotsBtnEl?.Height ?? -1}) " +
|
||||
$"btnVisible={slotsBtnEl?.Visible.ToString() ?? "n/a"}");
|
||||
|
||||
ApplyView(); // initial state = doll-view (armor slots hidden)
|
||||
|
||||
|
|
@ -128,8 +147,8 @@ public sealed class PaperdollController : IItemListDragHandler
|
|||
public static PaperdollController Bind(
|
||||
ImportedLayout layout, ClientObjectTable objects, Func<uint> playerGuid,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> iconIds, Action<uint, uint>? sendWield = null,
|
||||
uint emptySlotSprite = 0u)
|
||||
=> new PaperdollController(layout, objects, playerGuid, iconIds, sendWield, emptySlotSprite);
|
||||
uint emptySlotSprite = 0u, UiDatFont? datFont = null)
|
||||
=> new PaperdollController(layout, objects, playerGuid, iconIds, sendWield, emptySlotSprite, datFont);
|
||||
|
||||
private void OnObjectChanged(ClientObject o) { if (Concerns(o)) Populate(); }
|
||||
private void OnObjectMoved(ClientObject o, uint from, uint to)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,16 @@ namespace AcDream.App.Tests.Rendering;
|
|||
public class DollCameraTests
|
||||
{
|
||||
[Fact]
|
||||
public void Eye_position_is_the_retail_camera_offset()
|
||||
public void Eye_position_frames_the_doll_from_the_front()
|
||||
{
|
||||
var cam = new DollCamera { Aspect = 100f / 214f };
|
||||
Assert.True(Matrix4x4.Invert(cam.View, out var inv));
|
||||
var eye = inv.Translation;
|
||||
// Tuned at the visual gate: aim at mid-body (z≈0.95) and stand back ~3.7 m so the whole
|
||||
// figure fits (model origin is at the feet, so aiming at the origin framed only the legs).
|
||||
Assert.Equal(0.12f, eye.X, 3);
|
||||
Assert.Equal(-2.4f, eye.Y, 3);
|
||||
Assert.Equal(0.88f, eye.Z, 3);
|
||||
Assert.Equal(-3.7f, eye.Y, 3);
|
||||
Assert.Equal(0.95f, eye.Z, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue