fix(world): match retail portal passage and exit warp

This commit is contained in:
Erik 2026-07-15 22:14:21 +02:00
parent eab23cbdd1
commit 842bd89c16
11 changed files with 355 additions and 28 deletions

View file

@ -6,7 +6,7 @@ namespace AcDream.App.Rendering;
/// 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 rotates that view around +Z.
/// angle rolls that view around its own +Y forward axis.
/// </summary>
public sealed class PortalTunnelCamera : ICamera
{
@ -20,8 +20,9 @@ public sealed class PortalTunnelCamera : ICamera
/// <summary>
/// Retail <c>CreatureMode::UseSmartboxFOV</c> reads the active SmartBox
/// projection each draw. Recover its vertical field of view from that
/// perspective matrix while retaining this private scene's near/far planes.
/// projection each draw. Recover its vertical field of view and near
/// plane from that perspective matrix while retaining this private
/// scene's bounded far plane.
/// </summary>
public void UseSmartBoxFov(Matrix4x4 smartBoxProjection)
{
@ -32,6 +33,15 @@ public sealed class PortalTunnelCamera : ICamera
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;
if (float.IsFinite(near) && near > 0f && near < Far)
Near = near;
}
public Matrix4x4 View
@ -39,9 +49,17 @@ public sealed class PortalTunnelCamera : ICamera
get
{
float radians = DirectionDegrees * (MathF.PI / 180f);
Quaternion rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, radians);
Vector3 forward = Vector3.Transform(Vector3.UnitY, rotation);
return Matrix4x4.CreateLookAt(RetailEye, RetailEye + forward, Vector3.UnitZ);
// 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);
}
}