fix(world): remove non-retail portal exit fade

This commit is contained in:
Erik 2026-07-15 23:20:52 +02:00
parent 842bd89c16
commit dded9e6b17
21 changed files with 919 additions and 347 deletions

View file

@ -8,14 +8,16 @@ namespace AcDream.App.Rendering;
/// (<c>0x004D6E30</c>) eases SmartBox's view-plane distance between the
/// active game's value and <c>TRANSITION_VIEW_PLANE_DISTANCE = 0.001</c>.
/// <c>Render::set_vdst</c> (<c>0x0054B240</c>) converts that distance back to
/// FOV and adjusts the near plane. This is the wide projection warp visible
/// while the destination world emerges from black.
/// FOV and adjusts the near plane. At the exit edge retail swaps directly
/// from portal space to the destination world at the transition projection;
/// there is no black-alpha compositor between them.
/// </summary>
public sealed class TeleportViewPlaneController
{
public const float TransitionViewPlaneDistance = 0.001f;
private float _gameViewPlaneDistance = 1f;
private readonly ProjectionOverrideCamera _projectionCamera = new();
public bool Enabled { get; private set; }
public float CurrentViewPlaneDistance { get; private set; } = 1f;
@ -38,20 +40,42 @@ public sealed class TeleportViewPlaneController
}
/// <summary>
/// Apply the four retail fade-state branches. Their projection blend is
/// numerically the same table-driven level as the black fade: normal to
/// 0.001 on fade-out, and 0.001 back to normal on fade-in.
/// Apply retail's view-plane state machine. Here "fade" is retail's state
/// name for a projection transition: normal to 0.001 on fade-out, and
/// 0.001 back to normal on fade-in. A logout transition retains the
/// captured override during stable Tunnel; <c>EndTeleportAnimation</c>
/// (<c>0x004D65D5</c>) releases it when entering TunnelContinue.
/// </summary>
public void Update(TeleportAnimSnapshot snapshot)
{
Enabled = snapshot.State is TeleportAnimState.WorldFadeOut
or TeleportAnimState.TunnelFadeIn
or TeleportAnimState.TunnelFadeOut
or TeleportAnimState.WorldFadeIn;
switch (snapshot.State)
{
case TeleportAnimState.WorldFadeOut:
case TeleportAnimState.TunnelFadeIn:
case TeleportAnimState.TunnelFadeOut:
case TeleportAnimState.WorldFadeIn:
Enabled = true;
CurrentViewPlaneDistance = Lerp(
_gameViewPlaneDistance,
TransitionViewPlaneDistance,
snapshot.ViewPlaneBlend);
break;
CurrentViewPlaneDistance = Enabled
? Lerp(_gameViewPlaneDistance, TransitionViewPlaneDistance, snapshot.FadeAlpha)
: _gameViewPlaneDistance;
case TeleportAnimState.Tunnel:
// Retail preserves the captured FOV override through the
// logout path's stable tunnel. A normal portal begins in
// Tunnel with no override, so its disabled state is retained.
if (Enabled)
CurrentViewPlaneDistance = _gameViewPlaneDistance;
break;
case TeleportAnimState.TunnelContinue:
case TeleportAnimState.Off:
default:
Enabled = false;
CurrentViewPlaneDistance = _gameViewPlaneDistance;
break;
}
}
public void Reset()
@ -93,6 +117,39 @@ public sealed class TeleportViewPlaneController
return Matrix4x4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
}
/// <summary>
/// Decorate the active camera with the same projection returned by
/// <see cref="Apply(Matrix4x4)"/>. Retail's <c>Render::set_vdst</c> is
/// global to every 3-D draw; callers must pass this returned camera to
/// terrain, meshes, particles, sky, weather, and portal-cell rendering so
/// rasterization and frustum culling cannot use different projections.
/// </summary>
public ICamera ApplyTo(ICamera baseCamera)
{
ArgumentNullException.ThrowIfNull(baseCamera);
_projectionCamera.Update(baseCamera, Apply(baseCamera.Projection));
return _projectionCamera;
}
private static float Lerp(float from, float to, float amount) =>
from + (to - from) * Math.Clamp(amount, 0f, 1f);
private sealed class ProjectionOverrideCamera : ICamera
{
private ICamera _source = null!;
public Matrix4x4 View => _source.View;
public Matrix4x4 Projection { get; private set; } = Matrix4x4.Identity;
public float Aspect
{
get => _source.Aspect;
set => _source.Aspect = value;
}
public void Update(ICamera source, Matrix4x4 projection)
{
_source = source;
Projection = projection;
}
}
}