/*
====================================================================
  SXGG: Realis — Shader ultra-realistica per Minecraft
  Autore : Samax.GG
  File   : lib/pom.glsl
  Scopo  : Parallax Occlusion Mapping (Modulo 2) con:
           - raymarch a layer nella heightmap LabPBR (alpha di _n)
           - raffinamento binario del punto d'impatto (niente banding)
           - clamping all'atlante (evita bleeding tra texture adiacenti)
           - self-shadowing verso la luce
           - fade con la distanza (prestazioni)

  Convenzione LabPBR: normals.a = heightmap, 1.0 = superficie,
  0.0 = punto più profondo. Il POM "scava" verso il basso.

  Richiede: lib/settings.glsl
  Uniform richieste nel .fsh: normals (sampler2D)
====================================================================
*/

#ifndef POM_GLSL
#define POM_GLSL

/*
  Legge l'altezza dalla heightmap rimanendo dentro la singola
  texture dell'atlante. localCoord è in [0,1] nel tile;
  tileBase/tileSize descrivono il tile dentro l'atlante.
*/
float sampleHeight(vec2 localCoord, vec2 tileBase, vec2 tileSize) {
    vec2 atlasCoord = tileBase + fract(localCoord) * tileSize;
    return texture2DLod(normals, atlasCoord, 0.0).a;
}

/*
  POM principale.
    texCoord    : UV nell'atlante del frammento
    tileBase    : angolo del tile nell'atlante
    tileSize    : dimensione del tile nell'atlante
    tangentDir  : direzione di vista in tangent space (normalizzata)
    viewDist    : distanza dalla camera (per il fade)
  Ritorna le UV spostate e scrive parallaxDepth (0 = superficie).
*/
vec2 parallaxOcclusionMap(vec2 texCoord, vec2 tileBase, vec2 tileSize,
                          vec3 tangentDir, float viewDist,
                          out float parallaxDepth) {
    parallaxDepth = 0.0;

    // Fade in base alla distanza: oltre POM_DISTANCE il POM è spento
    float fade = saturate(1.0 - viewDist / POM_DISTANCE);
    if (fade < EPSILON || tangentDir.z >= -EPSILON) return texCoord;

    // Coordinate locali al tile [0,1]
    vec2 localCoord = (texCoord - tileBase) / tileSize;

    float depthScale = POM_DEPTH * fade;

    // Meno layer se guardiamo la superficie di striscio costa uguale,
    // quindi aumentiamo i layer alle angolazioni radenti (dove servono)
    float grazing   = 1.0 - abs(tangentDir.z);
    float numLayers = mix(float(POM_LAYERS) * 0.5, float(POM_LAYERS), grazing);

    float layerStep = 1.0 / numLayers;
    // Spostamento UV totale alla profondità massima
    vec2 deltaUV = (tangentDir.xy / -tangentDir.z) * depthScale / numLayers;

    float currentDepth = 0.0;
    vec2  currentUV    = localCoord;
    float height       = 1.0 - sampleHeight(currentUV, tileBase, tileSize);

    // --- Raymarch lineare: scendi finché il raggio non entra nel rilievo ---
    for (int i = 0; i < POM_LAYERS; i++) {
        if (currentDepth >= height) break;
        currentUV    += deltaUV;
        currentDepth += layerStep;
        height        = 1.0 - sampleHeight(currentUV, tileBase, tileSize);
    }

    // --- Raffinamento binario: 5 step dimezzano l'errore, zero "gradini" ---
    vec2  halfUV    = deltaUV   * 0.5;
    float halfStep  = layerStep * 0.5;
    for (int i = 0; i < 5; i++) {
        height = 1.0 - sampleHeight(currentUV, tileBase, tileSize);
        if (currentDepth < height) { currentUV += halfUV;  currentDepth += halfStep; }
        else                       { currentUV -= halfUV;  currentDepth -= halfStep; }
        halfUV   *= 0.5;
        halfStep *= 0.5;
    }

    parallaxDepth = currentDepth;
    return tileBase + fract(currentUV) * tileSize;
}

#ifdef POM_SELF_SHADOW
/*
  Self-shadowing: dal punto d'impatto risale verso la luce nella
  heightmap; se qualcosa lo blocca, il pixel è in micro-ombra.
  Ritorna 0 (ombra piena) .. 1 (illuminato). Soft-shadow: più il
  rilievo copre il raggio, più scura è l'ombra.
*/
float parallaxShadow(vec2 texCoord, vec2 tileBase, vec2 tileSize,
                     vec3 tangentLightDir, float startDepth, float viewDist) {
    float fade = saturate(1.0 - viewDist / POM_DISTANCE);
    if (fade < EPSILON || tangentLightDir.z <= EPSILON || startDepth <= EPSILON)
        return 1.0;

    vec2 localCoord = (texCoord - tileBase) / tileSize;
    float depthScale = POM_DEPTH * fade;

    const int SHADOW_STEPS = 8;
    float layerStep = startDepth / float(SHADOW_STEPS);
    vec2  deltaUV   = (tangentLightDir.xy / tangentLightDir.z)
                    * depthScale * layerStep;

    float currentDepth = startDepth;
    vec2  currentUV    = localCoord;
    float shadow       = 1.0;

    for (int i = 0; i < SHADOW_STEPS; i++) {
        currentUV    += deltaUV;
        currentDepth -= layerStep;
        float height = 1.0 - sampleHeight(currentUV, tileBase, tileSize);
        if (height < currentDepth) {
            // Penombra proporzionale a quanto il rilievo supera il raggio
            shadow = min(shadow, saturate((currentDepth - height) * -8.0 + 1.0));
            shadow = min(shadow, 0.35);
        }
    }
    // Il self-shadow sfuma con la distanza insieme al POM
    return mix(1.0, shadow, fade);
}
#endif // POM_SELF_SHADOW

#endif // POM_GLSL
