/* VertaLux — LPV (Light Propagation Volume): SHARED injection + read helpers.
   Addressing + sampleLPV (direct read) only. References ONLY lightImg + voxelImg
   (NOT lightImgB: the flood kernel lives in /lib/lpv_flood.glsl, included only by
   the propagation .csh — avoids 'undeclared identifier lightImgB' elsewhere).
   ADDRESSING must stay IDENTICAL to the clipmap snap (shadow.fsh / composite5
   sampleClip) but at LPV_SCALE with +LPV_HALF. Any deviation = drifting field =
   flicker.
   PREREQUISITES (declared by the INCLUDER before the #include):
   uniform vec3 cameraPosition; layout(rgba16f) uniform image3D voxelImg;
   layout(rgba16f) uniform image3D lightImg; and luma() (lib/common.glsl). */
#ifndef LPV_GLSL
#define LPV_GLSL

const float LPV_SCALE = VOXEL_SCALE * 2.0;   // 1.0 block/cell at VOXEL_SCALE=0.5 -> 128-block extent
const int   LPV_RES   = 128;
const int   LPV_HALF  = 64;

// World-space center (camera-relative) of an LPV cell. Exact inverse of the clipmap addressing at s=LPV_SCALE.
vec3 lpvCellToWorld(ivec3 c) {
    float s = LPV_SCALE;
    ivec3 camBlock = ivec3(floor(cameraPosition / s));
    return (vec3(c - LPV_HALF) + 0.5) * s - (cameraPosition - vec3(camBlock) * s);
}

// Reads CASCADE-0 voxel ONLY (exact copy of sampleClip's first branch, s=VOXEL_SCALE, +128).
// Used by injection (composite.csh) to tell whether an LPV cell overlaps an emissive/solid voxel.
vec4 sampleVoxC0(vec3 p) {
    float s = VOXEL_SCALE;
    ivec3 camBlock = ivec3(floor(cameraPosition / s));
    ivec3 cell = ivec3(floor((p + (cameraPosition - vec3(camBlock) * s)) / s)) + 128;
    if (all(greaterThanEqual(cell, ivec3(0))) && all(lessThan(cell, ivec3(256))))
        return imageLoad(voxelImg, cell);
    return vec4(0.0);
}

// MANUAL TRILINEAR read of the field (no HW filtering on custom image3D = nearest only).
// 8 imageLoad. lightImg encoding: .rgb=radiance, .a=flag (0 empty, 1 SOLID, 2 SOURCE).
// SOLID cells (a in [0.5,1.5]) are skipped -> prevents self-lighting and leaks through
// solid voxels. Renormalized by the weight sum of kept cells. *LPV_STRENGTH (calibration).
vec3 sampleLPV(vec3 p) {
    float s = LPV_SCALE;
    ivec3 camBlock = ivec3(floor(cameraPosition / s));
    vec3 g = (p + (cameraPosition - vec3(camBlock) * s)) / s + float(LPV_HALF) - 0.5;
    ivec3 b = ivec3(floor(g));
    vec3 f = g - vec3(b);
    vec3 acc = vec3(0.0);
    float ws = 0.0;
    for (int i = 0; i < 8; i++) {
        ivec3 o = ivec3(i & 1, (i >> 1) & 1, (i >> 2) & 1);
        ivec3 c = b + o;
        if (any(lessThan(c, ivec3(0))) || any(greaterThanEqual(c, ivec3(LPV_RES)))) continue;
        vec4 L = imageLoad(lightImg, c);
        if (L.a > 0.5 && L.a < 1.5) continue; // SOLID cell: never read its radiance
        float w = ((o.x == 1) ? f.x : 1.0 - f.x)
                * ((o.y == 1) ? f.y : 1.0 - f.y)
                * ((o.z == 1) ? f.z : 1.0 - f.z);
        acc += L.rgb * w;
        ws  += w;
    }
    return (ws > 1e-4) ? (acc / ws) * LPV_STRENGTH : vec3(0.0);
}

#endif // LPV_GLSL
