Port retail portal viewport projection and reveal behavior, preserve outbound combat style, drive remote and local grounded movement from authored CSequence root frames, and reuse the local prepared pose so animation hooks advance once. User-verified portal, observer movement, combat stance, and short-tap locomotion gates. Release build passed with 5,767 tests and five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
80 lines
3.4 KiB
C#
80 lines
3.4 KiB
C#
using System.Numerics;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Retail portal-space camera from <c>gmSmartBoxUI::PostInit</c>
|
|
/// (<c>0x004D6D8A</c>) and <c>CreatureMode::SetCameraDirection_Degrees</c>
|
|
/// (<c>0x00453760</c>). Identity looks along AC +Y with +Z up; the animated
|
|
/// angle rolls that view around its own +Y forward axis.
|
|
/// </summary>
|
|
public sealed class PortalTunnelCamera : ICamera
|
|
{
|
|
public static readonly Vector3 RetailEye = new(0.24f, -2.7f, 0.88f);
|
|
|
|
public float DirectionDegrees { get; set; }
|
|
public float FovRadians { get; set; } = MathF.PI / 4f;
|
|
public float Near { get; set; } = 0.1f;
|
|
// Render::zfar defaults to 4000 in retail (0x0081EC88). CreatureMode::Render
|
|
// does not install a private far plane: PrimD3DRender::SetFOVInternal
|
|
// (0x0059AB40) rebuilds the portal projection with that shared value.
|
|
public float Far { get; set; } = 4000f;
|
|
public float Aspect { get; set; } = 1f;
|
|
|
|
/// <summary>
|
|
/// Retail <c>CreatureMode::UseSmartboxFOV</c> reads the active SmartBox
|
|
/// projection each draw. Recover its vertical field of view and near
|
|
/// and far planes from that perspective matrix. Retail's CreatureMode
|
|
/// changes the FOV through the SmartBox path while retaining the renderer's
|
|
/// shared near/far clip range.
|
|
/// </summary>
|
|
public void UseSmartBoxFov(Matrix4x4 smartBoxProjection)
|
|
{
|
|
float verticalScale = smartBoxProjection.M22;
|
|
if (!float.IsFinite(verticalScale) || verticalScale <= 0f)
|
|
return;
|
|
|
|
float fov = 2f * MathF.Atan(1f / verticalScale);
|
|
if (float.IsFinite(fov) && fov > 0f && fov < MathF.PI)
|
|
FovRadians = fov;
|
|
|
|
// System.Numerics perspective matrices encode near as M43/M33.
|
|
// During teleport fades this carries Render::set_vdst's
|
|
// max(0.1, viewPlaneDistance * 0.25) result.
|
|
float near = smartBoxProjection.M33 != 0f
|
|
? smartBoxProjection.M43 / smartBoxProjection.M33
|
|
: float.NaN;
|
|
float far = smartBoxProjection.M33 != -1f
|
|
? smartBoxProjection.M43 / (smartBoxProjection.M33 + 1f)
|
|
: float.NaN;
|
|
if (float.IsFinite(far) && far > 0f)
|
|
Far = far;
|
|
if (float.IsFinite(near) && near > 0f && near < Far)
|
|
Near = near;
|
|
}
|
|
|
|
public Matrix4x4 View
|
|
{
|
|
get
|
|
{
|
|
float radians = DirectionDegrees * (MathF.PI / 180f);
|
|
// CreatureMode::SetCameraDirection_Degrees (0x00453760) resets
|
|
// the Frame to identity, converts (0, angle, 0) to radians, and
|
|
// passes that rotation vector to Frame::rotate (0x004525B0).
|
|
// Frame::rotate interprets the vector as axis * angle, so this is
|
|
// a roll around AC +Y -- the identity camera's FORWARD axis --
|
|
// not a yaw around world +Z. The passage therefore stays ahead
|
|
// while the authored tunnel rolls around the viewer.
|
|
Quaternion rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, radians);
|
|
Vector3 forward = Vector3.UnitY;
|
|
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
|
return Matrix4x4.CreateLookAt(RetailEye, RetailEye + forward, up);
|
|
}
|
|
}
|
|
|
|
public Matrix4x4 Projection => Matrix4x4.CreatePerspectiveFieldOfView(
|
|
FovRadians,
|
|
Aspect <= 0f ? 1f : Aspect,
|
|
Near,
|
|
Far);
|
|
}
|