// Lens flares (depth-anchored) – RAYCAST X ONLY (sin MSAA)



// ===== Recursos =====
#define cmp -
Texture1D<float4> IniParams      : register(t120);
Texture2D<float4> StereoParams   : register(t125);
Texture2D<float>  DepthBuffer    : register(t110);

// ===== Config =====
#define DEPTH_REVERSED 1
static const float MAX_SHIFT   = 0.08;   // tope NDC por ojo
static const float near_z      = 0.0001; // ajusta si conoces los reales
static const float far_z       = 1.0;    // idem
// ===================

// ------------------ utilidades depth ------------------

// Convierte NDC [-1,1] -> texel coords [0..w-1]/[0..h-1] (Y invertida)
int2 ndc_to_texel(float2 ndc, uint W, uint H)
{
    float sx = saturate(ndc.x * 0.5 + 0.5) * (W - 1);
    float sy = saturate(-ndc.y * 0.5 + 0.5) * (H - 1);
    return int2(sx, sy);
}

// Lee el valor crudo del depth buffer (resuelto)
float sample_depth_raw_texel(int2 tc)
{
    return DepthBuffer.Load(int3(tc, 0)).x;
}

// Aproxima una “distancia” lineal desde el valor de depth crudo.
// Devuelve 0 cuando detecta fondo/cielo (z == 1).
float depth_linear_from_raw(float z)
{
    if (z == 1.0)
        return 0.0; // sentinel: sin geometría válida

#if DEPTH_REVERSED
    z = 1.0 - z;
#endif

    // Aproximación; idealmente derivar desde la proyección si la tienes
    return (far_z * near_z) / max(1e-8, (z * near_z)) + (far_z * z);
}

// Lee depth en (x,y) NDC y devuelve “w” (distancia aprox). 0 si no válido.
float world_z_from_depth_buffer(float x, float y)
{
    uint W, H; DepthBuffer.GetDimensions(W, H);
    int2 tc = ndc_to_texel(float2(x, y), W, H);
    float z = sample_depth_raw_texel(tc);
    return depth_linear_from_raw(z);
}

// ------------------ raycast en X (NDC) ------------------
float adjust_from_depth_buffer(float x, float y)
{
    // Se asume: StereoParams.Load(0).x = separación con signo de ojo (+/-)
    //           StereoParams.Load(0).y = convergencia
    float4 stereo = StereoParams.Load(0);
    if (stereo.x == 0.0) return 0.0;

    float separation  = stereo.x;            // firmado
    float convergence = stereo.y;

    // Dirección del ojo (+1/-1) según el signo de separation
    float signEye = (separation >= 0.0) ? 1.0 : -1.0;
    float absSep  = abs(separation);

    // Punto de arranque del barrido y recorrido total (magnitud)
    float offset   = (near_z - convergence) * absSep;
    float distance = absSep - offset;

    float old_offset = offset;
    for (uint i = 0; i < 200; ++i)
    {
        offset += distance / 200.0;

        // Profundidad “esperada” (modelo empírico)
        float w_expected = (absSep * convergence * 10.0) / max(1e-6, (absSep - offset));

        // Profundidad real de la escena en la X barrida (con signo del ojo)
        float w_scene = world_z_from_depth_buffer(x + signEye * offset, y);

        // Si no hay geometría, fallback: empuja hasta el máximo (absSep)
        if (w_scene == 0.0)
            return absSep;

        // Choque: la escena es más cercana que lo esperado
        if (w_expected > w_scene)
            break;

        old_offset = offset;
    }

    // Devuelve desplazamiento ESCALAR (positivo) en NDC-X
    return old_offset;
}

// ------------------ VS ------------------
void main(
    float3 v0 : POSITION0,
    float4 v1 : TEXCOORD0,
    float4 v2 : TEXCOORD1,
    float3 v3 : TEXCOORD2,
    out float4 o0 : SV_Position0,
    out float2 o1 : TEXCOORD0,
    out float2 p1 : TEXCOORD2,
    out float3 o2 : TEXCOORD1)
{
    // Posición pantalla (ya en NDC)
    o0.xy = v0.xy;
    o0.zw = float2(1, 1);

    // Raycast X (NDC) con dirección por ojo
    float4 stereo = StereoParams.Load(0);
    float signEye = (stereo.x >= 0.0) ? 1.0 : -1.0;

    float off = adjust_from_depth_buffer(o0.x, o0.y);  // escalar positivo
    off = clamp(off, -MAX_SHIFT, MAX_SHIFT);           // cinturón de seguridad

    // Aplica signo del ojo al desplazamiento en X
    o0.x += signEye * off;

    // Passthrough a la PS
    o1 = v1.xy;
    p1 = v2.xy;
    o2 = v3.xyz;
}
