using System.Numerics;
namespace AcDream.App.Rendering;
///
/// Retail portal-space camera from gmSmartBoxUI::PostInit
/// (0x004D6D8A) and CreatureMode::SetCameraDirection_Degrees
/// (0x00453760). Identity looks along AC +Y with +Z up; the animated
/// angle rolls that view around its own +Y forward axis.
///
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;
///
/// Retail CreatureMode::UseSmartboxFOV 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.
///
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);
}