Wires the dormant TeleportAnimSequencer as the transit driver: on PlayerTeleport the player holds in PortalSpace behind a full-screen fade (FadeOverlay) until the destination terrain is resident (TeleportWorldReady, gated on the priority-applied landblock), then materializes (Place), and after the world fades back in regains control + acks the server (FireLoginComplete). No movement resolves against the empty world, so the outbound cell frame can't corrupt. Outdoor changes from place-immediately back to hold-until-resident (now fast, not a band-aid). - FadeOverlay: fullscreen NDC black quad, alpha = ShowTunnel ? 1 : FadeAlpha. - Retires TeleportArrivalController + its 2 tests (TAS subsumes the driver role). - Divergence register: AD-2 updated to the new mechanism; AD-31 (fade vs swirl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
121 lines
4.3 KiB
C#
121 lines
4.3 KiB
C#
using System;
|
|
using Silk.NET.OpenGL;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Fullscreen black quad at a given alpha — the teleport fade cover (retail-teleport
|
|
/// spec §C, 2026-06-22). Drawn LAST in the frame (over the world + UI) so it covers
|
|
/// everything; the <see cref="AcDream.Core.World.TeleportAnimSequencer"/> drives the alpha
|
|
/// (opaque-black through the transit, ramping the world back in on WorldFadeIn). The
|
|
/// authentic 3D portal-swirl is deferred — this black cover replaces it for now.
|
|
///
|
|
/// <para>Draws in NDC, so it needs no view/projection. Self-contained GL state
|
|
/// (feedback_render_self_contained_gl_state): sets blend + disables depth, restores the
|
|
/// frame-global convention (depth test/write on) on exit.</para>
|
|
/// </summary>
|
|
public sealed class FadeOverlay : IDisposable
|
|
{
|
|
private const string VertSrc = @"#version 430 core
|
|
layout(location = 0) in vec2 aPos;
|
|
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }";
|
|
|
|
private const string FragSrc = @"#version 430 core
|
|
uniform float uAlpha;
|
|
out vec4 FragColor;
|
|
void main() { FragColor = vec4(0.0, 0.0, 0.0, uAlpha); }";
|
|
|
|
private readonly GL _gl;
|
|
private readonly uint _program;
|
|
private readonly uint _vao;
|
|
private readonly uint _vbo;
|
|
private readonly int _locAlpha;
|
|
|
|
// Two triangles covering the whole NDC viewport.
|
|
private static readonly float[] QuadVerts =
|
|
{
|
|
-1f, -1f, 1f, -1f, 1f, 1f,
|
|
-1f, -1f, 1f, 1f, -1f, 1f,
|
|
};
|
|
|
|
public FadeOverlay(GL gl)
|
|
{
|
|
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
|
|
|
uint vs = Compile(ShaderType.VertexShader, VertSrc);
|
|
uint fs = Compile(ShaderType.FragmentShader, FragSrc);
|
|
_program = _gl.CreateProgram();
|
|
_gl.AttachShader(_program, vs);
|
|
_gl.AttachShader(_program, fs);
|
|
_gl.LinkProgram(_program);
|
|
_gl.GetProgram(_program, ProgramPropertyARB.LinkStatus, out int linked);
|
|
if (linked == 0)
|
|
throw new InvalidOperationException($"FadeOverlay link failed: {_gl.GetProgramInfoLog(_program)}");
|
|
_gl.DeleteShader(vs);
|
|
_gl.DeleteShader(fs);
|
|
|
|
_locAlpha = _gl.GetUniformLocation(_program, "uAlpha");
|
|
|
|
_vao = _gl.GenVertexArray();
|
|
_vbo = _gl.GenBuffer();
|
|
_gl.BindVertexArray(_vao);
|
|
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
|
|
unsafe
|
|
{
|
|
fixed (float* v = QuadVerts)
|
|
_gl.BufferData(BufferTargetARB.ArrayBuffer,
|
|
(nuint)(QuadVerts.Length * sizeof(float)), v, BufferUsageARB.StaticDraw);
|
|
_gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 2 * sizeof(float), (void*)0);
|
|
}
|
|
_gl.EnableVertexAttribArray(0);
|
|
_gl.BindVertexArray(0);
|
|
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
|
}
|
|
|
|
private uint Compile(ShaderType type, string src)
|
|
{
|
|
uint s = _gl.CreateShader(type);
|
|
_gl.ShaderSource(s, src);
|
|
_gl.CompileShader(s);
|
|
_gl.GetShader(s, ShaderParameterName.CompileStatus, out int ok);
|
|
if (ok == 0)
|
|
throw new InvalidOperationException($"FadeOverlay {type} compile failed: {_gl.GetShaderInfoLog(s)}");
|
|
return s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the fullscreen black cover at <paramref name="alpha"/> (0 = clear → no-op,
|
|
/// 1 = opaque). Must be called LAST in the frame, after the world and UI, so it covers
|
|
/// everything on screen.
|
|
/// </summary>
|
|
public void Draw(float alpha)
|
|
{
|
|
if (alpha <= 0f) return;
|
|
alpha = Math.Clamp(alpha, 0f, 1f);
|
|
|
|
// ---- set state (everything this draw depends on) ----
|
|
_gl.Disable(EnableCap.DepthTest);
|
|
_gl.DepthMask(false);
|
|
_gl.Disable(EnableCap.CullFace);
|
|
_gl.Enable(EnableCap.Blend);
|
|
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
|
|
_gl.UseProgram(_program);
|
|
_gl.Uniform1(_locAlpha, alpha);
|
|
_gl.BindVertexArray(_vao);
|
|
_gl.DrawArrays(PrimitiveType.Triangles, 0, 6);
|
|
_gl.BindVertexArray(0);
|
|
_gl.UseProgram(0);
|
|
|
|
// ---- restore the frame-global convention ----
|
|
_gl.DepthMask(true);
|
|
_gl.Enable(EnableCap.DepthTest);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_gl.DeleteProgram(_program);
|
|
_gl.DeleteVertexArray(_vao);
|
|
_gl.DeleteBuffer(_vbo);
|
|
}
|
|
}
|