// "Sampling Visible GGX Normals with Spherical Caps" 
// https://arxiv.org/abs/2306.05044

// Sampling the visible hemisphere as half vectors (our method)
vec3 SampleVndf_Hemisphere(vec2 u, vec3 wi) {
    // sample a spherical cap in (-wi.z, 1]
    float phi = 2.0f * PI * u.x;
    float z = fma((1.0f - u.y), (1.0f + wi.z), -wi.z);
    float sinTheta = sqrt(clamp(1.0f - z * z, 0.0f, 1.0f));
    float x = sinTheta * cos(phi);
    float y = sinTheta * sin(phi);
    vec3 c = vec3(x, y, z);
    // compute halfway direction;
    vec3 h = c + wi;
    // return without normalization (as this is done later)
    return h;
}

vec3 SampleVndf_GGX(vec2 u, vec3 wi, vec2 alpha) {
    // warp to the hemisphere configuration
    vec3 wiStd = normalize(vec3(wi.xy * alpha, wi.z));
    // sample the hemisphere (see implementation 2 or 3)
    vec3 wmStd = SampleVndf_Hemisphere(u, wiStd);
    // warp back to the ellipsoid configuration
    vec3 wm = normalize(vec3(wmStd.xy * alpha, wmStd.z));
    // return final normal
    return wm;
}

vec3 ssr(vec3 RVec, vec3 ViewPos, vec3 ScreenPos, float LightmapSky, bool IsHand, float Smoothness) {
    int Steps = int(SSR_STEPS * (Smoothness / 2 + 0.5));
    vec3 Offset = normalize(view_screen(ViewPos + RVec, true) - ScreenPos);
    vec3 Len = (step(0, Offset) - ScreenPos) / Offset;
    float MinLen = min(Len.x, min(Len.y, Len.z)) / Steps;
    Offset *= MinLen;

    float Noise = dither(gl_FragCoord.xy);

    vec3 ExpectedPos = ScreenPos + Offset * Noise;
    for (int i = 1; i <= Steps; i++) {
        float RealDepth = texture(depthtex1, ExpectedPos.xy).x;
        if (RealDepth < 0.56) {
            break;
        }
        if (ExpectedPos.z > RealDepth) {
            // Depth based rejection
            if (ExpectedPos.z - RealDepth > abs(Offset.z) * 3) break;

            // Binary refinement
            for (int i = 1; i <= Steps / 6; i++) {
                Offset /= 2;
                vec3 EPos1 = ExpectedPos - Offset;
                float RDepth1 = texture(depthtex1, EPos1.xy).x;
                if (EPos1.z > RDepth1) {
                    ExpectedPos = EPos1;
                }
            }
            vec3 SkyColor = get_sky(RVec, false, 1);

            vec3 TerrainColor = texture(colortex0, ExpectedPos.xy).rgb;
            vec3 StartPos = view_player(ViewPos);
            vec3 EndPos = view_player(screen_view(ExpectedPos, true));
            vec3 RefColor = do_vl(StartPos, EndPos, view_player(RVec), TerrainColor, 0, 3);

            float Dist = length(EndPos) / far;
            return get_border_fog(Dist, RefColor, SkyColor);
        }
        ExpectedPos += Offset;
    }
    if (LightmapSky < 0.1) return vec3(0);

    vec3 StartPos = view_player(ViewPos);
    vec3 EndPos = view_player(RVec);
    vec3 SkyColor = get_sky(RVec, false, EndPos.y);
    #if (defined CLOUDS) && (defined DIMENSION_OVERWORLD) 
        vec3 P = EndPos;
        vec2 CloudPos = vec2(P.x, P.z) / (1 + P.y);
        SkyColor += texture(colortex3, (CloudPos * 0.5 + 0.5) * resolutionInv * CLOUD_TEX_SIZE).rgb;
    #endif

    //SkyColor += get_stars(EndPos); // Not bright enough to be noticed
    return do_vl(StartPos, vec3(0, 10000, 0), EndPos, SkyColor, 0, 3) * LightmapSky;
}
