fix(world): match retail portal passage and exit warp
This commit is contained in:
parent
eab23cbdd1
commit
842bd89c16
11 changed files with 355 additions and 28 deletions
|
|
@ -6922,6 +6922,7 @@ public sealed class GameWindow : IDisposable
|
|||
// fires Place (materialize) and FireLoginComplete (regain control + ack the server).
|
||||
// Replaces the old TeleportArrivalController hold/place machine.
|
||||
private readonly AcDream.Core.World.TeleportAnimSequencer _teleportAnim = new();
|
||||
private readonly TeleportViewPlaneController _teleportViewPlane = new();
|
||||
private bool _teleportInProgress;
|
||||
private System.Numerics.Vector3 _pendingTeleportPos;
|
||||
private uint _pendingTeleportCell;
|
||||
|
|
@ -7022,11 +7023,12 @@ public sealed class GameWindow : IDisposable
|
|||
_playerController.Yaw = AcQuaternionToYaw(_pendingTeleportRot);
|
||||
|
||||
_chaseCamera?.Update(snappedPos, _playerController.Yaw);
|
||||
_retailChaseCamera?.Update(snappedPos, _playerController.Yaw,
|
||||
playerVelocity: System.Numerics.Vector3.Zero,
|
||||
isOnGround: true,
|
||||
contactPlaneNormal: System.Numerics.Vector3.UnitZ,
|
||||
dt: 1f / 60f);
|
||||
// SmartBox::PlayerPositionUpdated (0x00453870) calls
|
||||
// set_viewer(player_pos, reset_sought=1): the viewer and sought
|
||||
// position both restart at the player, then the ordinary per-frame
|
||||
// camera path re-extends the boom. Updating directly from the stale
|
||||
// source-world viewer made the destination bend across the reveal.
|
||||
_retailChaseCamera?.ResetViewerToPlayer(snappedPos, _playerController.Yaw);
|
||||
|
||||
AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport(
|
||||
"PLACED", resolved.CellId, $"forced={forced}");
|
||||
|
|
@ -7055,6 +7057,8 @@ public sealed class GameWindow : IDisposable
|
|||
_teleportInProgress = true;
|
||||
_teleportHoldSeconds = 0f;
|
||||
_teleportForced = false;
|
||||
_teleportViewPlane.Begin(
|
||||
_cameraController?.Active.Projection ?? System.Numerics.Matrix4x4.Identity);
|
||||
_teleportAnim.Begin(AcDream.Core.World.TeleportEntryKind.Portal);
|
||||
Console.WriteLine($"live: teleport started (seq={sequence})");
|
||||
}
|
||||
|
|
@ -8900,6 +8904,7 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
int tunnelFrame = _portalTunnel?.CurrentAnimationFrame ?? 0;
|
||||
var (snap, evts) = _teleportAnim.Tick((float)dt, ready, tunnelFrame);
|
||||
_teleportViewPlane.Update(snap);
|
||||
// The fade is a viewport transition below retained UI. If the
|
||||
// installed DATs are corrupt and the retail portal scene could
|
||||
// not be built, retain an opaque world cover during transit.
|
||||
|
|
@ -8934,6 +8939,7 @@ public sealed class GameWindow : IDisposable
|
|||
AcDream.Core.Net.Messages.GameActionLoginComplete.Build());
|
||||
_teleportInProgress = false;
|
||||
_pendingTeleportCell = 0u;
|
||||
_teleportViewPlane.Reset();
|
||||
_teleportFadeAlpha = 0f;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -9570,7 +9576,8 @@ public sealed class GameWindow : IDisposable
|
|||
if (_cameraController is not null)
|
||||
{
|
||||
var camera = _cameraController.Active;
|
||||
var frustum = AcDream.App.Rendering.FrustumPlanes.FromViewProjection(camera.View * camera.Projection);
|
||||
var worldProjection = _teleportViewPlane.Apply(camera.Projection);
|
||||
var frustum = AcDream.App.Rendering.FrustumPlanes.FromViewProjection(camera.View * worldProjection);
|
||||
|
||||
// Extract camera world position from the inverse of the view
|
||||
// matrix — needed by the scene-lighting UBO (for fog distance)
|
||||
|
|
@ -9799,7 +9806,7 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
// Phase A8: update EnvCellRenderer's frustum. The per-frame shell snapshot
|
||||
// is prepared after the portal-visible cell filter is known.
|
||||
var envCellViewProj = camera.View * camera.Projection;
|
||||
var envCellViewProj = camera.View * worldProjection;
|
||||
_envCellFrustum?.Update(envCellViewProj);
|
||||
|
||||
// MP-Alloc: reuse _animatedIdsScratch instead of `new`ing a
|
||||
|
|
@ -10422,7 +10429,7 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
_debugDrawLogOnce++;
|
||||
}
|
||||
_debugLines.Flush(camera.View, camera.Projection);
|
||||
_debugLines.Flush(camera.View, worldProjection);
|
||||
}
|
||||
|
||||
// Count visible vs total for the perf overlay.
|
||||
|
|
@ -10484,10 +10491,12 @@ public sealed class GameWindow : IDisposable
|
|||
// CreatureMode portal-space viewport. This replacement scene and
|
||||
// its black transition draw BEFORE retained UI, so chat, windows,
|
||||
// cursor, and toolbar remain visible and interactive in transit.
|
||||
var portalProjection = _teleportViewPlane.Apply(
|
||||
_cameraController!.Active.Projection);
|
||||
_portalTunnel?.Draw(
|
||||
_window!.Size.X,
|
||||
_window.Size.Y,
|
||||
_cameraController!.Active.Projection);
|
||||
portalProjection);
|
||||
_fadeOverlay?.Draw(_teleportFadeAlpha);
|
||||
|
||||
// Phase D.2b Sub-phase C Slice 2 — paperdoll 3-D doll: render the re-dressed player clone into the
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -247,6 +247,32 @@ public sealed class RetailChaseCamera : ICamera
|
|||
PlayerTranslucency = ComputeTranslucency(d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>SmartBox::set_viewer(playerPosition, reset_sought: 1)</c>
|
||||
/// (<c>0x00452C40</c>), called by
|
||||
/// <c>SmartBox::PlayerPositionUpdated</c> after a teleport
|
||||
/// (<c>0x00453870</c>). Both the published viewer and its sought
|
||||
/// position restart at the player; subsequent camera updates then
|
||||
/// re-extend the boom through the ordinary damped/swept path.
|
||||
/// </summary>
|
||||
public void ResetViewerToPlayer(Vector3 playerPosition, float playerYaw)
|
||||
{
|
||||
Vector3 playerForward = new(MathF.Cos(playerYaw), MathF.Sin(playerYaw), 0f);
|
||||
|
||||
_publishedEye = playerPosition;
|
||||
_soughtEye = playerPosition;
|
||||
_dampedForward = playerForward;
|
||||
_initialised = true;
|
||||
|
||||
Position = playerPosition;
|
||||
ViewerCellId = 0u; // retail set_viewer clears viewer_cell at the teleport boundary
|
||||
View = Matrix4x4.CreateLookAt(
|
||||
playerPosition,
|
||||
playerPosition + playerForward,
|
||||
Vector3.UnitZ);
|
||||
PlayerTranslucency = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the camera distance (zoom) by a delta, clamped to
|
||||
/// <see cref="DistanceMin"/>..<see cref="DistanceMax"/>. Mirrors
|
||||
|
|
|
|||
98
src/AcDream.App/Rendering/TeleportViewPlaneController.cs
Normal file
98
src/AcDream.App/Rendering/TeleportViewPlaneController.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Ports retail's teleport projection transition. <c>gmSmartBoxUI::UseTime</c>
|
||||
/// (<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.
|
||||
/// </summary>
|
||||
public sealed class TeleportViewPlaneController
|
||||
{
|
||||
public const float TransitionViewPlaneDistance = 0.001f;
|
||||
|
||||
private float _gameViewPlaneDistance = 1f;
|
||||
|
||||
public bool Enabled { get; private set; }
|
||||
public float CurrentViewPlaneDistance { get; private set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>BeginTeleportAnimation</c> captures
|
||||
/// <c>SmartBox::GetOverrideFovDistance</c> once when starting from Off.
|
||||
/// For a standard perspective matrix, M22 is exactly
|
||||
/// <c>cot(verticalFov / 2)</c>, retail's view-plane-distance value.
|
||||
/// </summary>
|
||||
public void Begin(Matrix4x4 gameProjection)
|
||||
{
|
||||
float distance = gameProjection.M22;
|
||||
if (!float.IsFinite(distance) || distance <= 0f)
|
||||
distance = 1f;
|
||||
|
||||
_gameViewPlaneDistance = distance;
|
||||
CurrentViewPlaneDistance = distance;
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public void Update(TeleportAnimSnapshot snapshot)
|
||||
{
|
||||
Enabled = snapshot.State is TeleportAnimState.WorldFadeOut
|
||||
or TeleportAnimState.TunnelFadeIn
|
||||
or TeleportAnimState.TunnelFadeOut
|
||||
or TeleportAnimState.WorldFadeIn;
|
||||
|
||||
CurrentViewPlaneDistance = Enabled
|
||||
? Lerp(_gameViewPlaneDistance, TransitionViewPlaneDistance, snapshot.FadeAlpha)
|
||||
: _gameViewPlaneDistance;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Enabled = false;
|
||||
CurrentViewPlaneDistance = _gameViewPlaneDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild a perspective projection with retail's active view-plane
|
||||
/// distance while preserving the source projection's aspect and far
|
||||
/// plane. <c>Render::set_vdst</c> uses
|
||||
/// <c>FOV = 2 * atan(1 / distance)</c> and
|
||||
/// <c>znear = max(0.1, distance * 0.25)</c>.
|
||||
/// </summary>
|
||||
public Matrix4x4 Apply(Matrix4x4 baseProjection)
|
||||
{
|
||||
if (!Enabled)
|
||||
return baseProjection;
|
||||
|
||||
float aspect = baseProjection.M11 != 0f
|
||||
? baseProjection.M22 / baseProjection.M11
|
||||
: 1f;
|
||||
float far = baseProjection.M33 != -1f
|
||||
? baseProjection.M43 / (baseProjection.M33 + 1f)
|
||||
: 5000f;
|
||||
|
||||
if (!float.IsFinite(aspect) || aspect <= 0f)
|
||||
aspect = 1f;
|
||||
if (!float.IsFinite(far) || far <= 0.1f)
|
||||
far = 5000f;
|
||||
|
||||
float distance = MathF.Max(CurrentViewPlaneDistance, TransitionViewPlaneDistance);
|
||||
float fov = 2f * MathF.Atan(1f / distance);
|
||||
float near = MathF.Max(0.1f, distance * 0.25f);
|
||||
if (near >= far)
|
||||
near = MathF.Min(0.1f, far * 0.5f);
|
||||
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
|
||||
}
|
||||
|
||||
private static float Lerp(float from, float to, float amount) =>
|
||||
from + (to - from) * Math.Clamp(amount, 0f, 1f);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue