/*
  Screen-Space Contact Shadows
  Traces a short ray in screen space toward the light source to detect
  micro-occlusion that the shadow map misses (e.g. fences, plants, thin geometry).
  Intended for use in the deferred pass where depthtex0, gbufferProjection, and
  GetLinearDepth are available.
*/

#ifndef CONTACT_SHADOWS_GLSL
#define CONTACT_SHADOWS_GLSL

float GetContactShadow(vec3 viewPos, vec3 lightDir, float dither) {
    // Fade out contact shadows on distant surfaces
    float viewDist = length(viewPos);
    if (viewDist > 32.0) return 1.0;
    float distFade = 1.0 - smoothstep(16.0, 32.0, viewDist);

    // Ray length scales with distance but caps out
    float rayLength = CONTACT_SHADOW_LENGTH * min(viewDist * 0.25, 1.0);
    vec3 rayStep = normalize(lightDir) * (rayLength / float(CONTACT_SHADOW_STEPS));

    // Start with dither offset to reduce banding
    vec3 rayPos = viewPos + rayStep * dither;

    float shadow = 1.0;

    for (int i = 0; i < CONTACT_SHADOW_STEPS; i++) {
        rayPos += rayStep;

        // Project view-space position to screen coordinates
        vec4 clipPos = gbufferProjection * vec4(rayPos, 1.0);
        vec3 screenPos = clipPos.xyz / clipPos.w * 0.5 + 0.5;

        // Bail if we leave the screen
        if (screenPos.x < 0.0 || screenPos.x > 1.0 ||
            screenPos.y < 0.0 || screenPos.y > 1.0) break;

        // Compare ray depth with scene depth
        float sceneDepth = GetLinearDepth(texture2D(depthtex0, screenPos.xy).r);
        float rayDepth   = GetLinearDepth(screenPos.z);

        float depthDiff = rayDepth - sceneDepth;

        // Hit: ray is behind scene geometry but not too far behind (thickness test)
        if (depthDiff > 0.0001 && depthDiff < CONTACT_SHADOW_THICKNESS) {
            // Smooth falloff: stronger hits near the start of the ray
            float hitFade = 1.0 - float(i + 1) / float(CONTACT_SHADOW_STEPS);
            shadow = 1.0 - hitFade * distFade;
            break;
        }
    }

    return shadow;
}

#endif
