#if DYN_LIGHT_SAMPLE_MAX > 0 && DYN_LIGHT_MODE == DYN_LIGHT_TRACED && DYN_LIGHT_TA > 0 && !(defined RENDER_TRANSLUCENT || defined RENDER_VERTEX)
    #define DYN_LIGHT_INTERLEAVE_ENABLED
#endif

float InterleavedGradientNoiseTime(const in vec2 pixel) {
    vec2 p = pixel + frameCounter * 5.588238;
    float x = dot(p, magic.xy);
    return fract(magic.z * fract(x));
}

void SampleDynamicLighting(inout vec3 blockDiffuse, inout vec3 blockSpecular, const in vec3 localPos, const in vec3 localNormal, const in vec3 texNormal, const in vec3 albedo, const in float roughL, const in float metal_f0, const in float sss, const in vec3 blockLightDefault) {
    uint gridIndex;
    float viewDist = length(localPos);
    vec3 localViewDir = -normalize(localPos);
    float distBiasScale = 0.02 + min(0.01*viewDist, 0.2);

    vec3 lightFragPos = localPos;
    lightFragPos += distBiasScale*localNormal;
    //lightFragPos += distBiasScale*localViewDir;

    uint lightCount = GetVoxelLights(lightFragPos, gridIndex);

    if (gridIndex != DYN_LIGHT_GRID_MAX) {
        bool hasGeoNormal = !all(lessThan(abs(localNormal), EPSILON3));
        bool hasTexNormal = !all(lessThan(abs(texNormal), EPSILON3));

        #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
            vec3 f0 = GetMaterialF0(albedo, metal_f0);
        #endif

        float lightNoVm = 1.0;
        if (hasTexNormal) lightNoVm = max(dot(texNormal, localViewDir), EPSILON);

        vec3 accumDiffuse = vec3(0.0);
        vec3 accumSpecular = vec3(0.0);

        vec3 traceEnd = GetVoxelBlockPosition(lightFragPos);
        vec3 cameraOffset = fract(cameraPosition);

        uint iOffset = 0u;
        uint iStep = 1u;
        #ifdef DYN_LIGHT_INTERLEAVE_ENABLED
            uint interleaveCount = uint(ceil(lightCount / float(DYN_LIGHT_SAMPLE_MAX)));

            if (interleaveCount > 1u) {
                float n = InterleavedGradientNoiseTime(gl_FragCoord.xy);
                iOffset = uint(n * interleaveCount);
                iStep = interleaveCount;
            }
        #endif

        #if DYN_LIGHT_SAMPLE_MAX > 0 && DYN_LIGHT_TA > 0
            const int MaxSampleCount = min(DYN_LIGHT_SAMPLE_MAX, LIGHT_BIN_MAX_COUNT);
        #else
            const int MaxSampleCount = LIGHT_BIN_MAX_COUNT;
        #endif

        for (uint i = 0u; i < min(lightCount, MaxSampleCount); i++) {
            vec3 lightPos, lightColor, lightVec;
            float lightSize, lightRange, traceDist2;
            uvec4 lightData;

            //bool hasLight = false;
            //for (uint i2 = 0u; i2 < 16u; i2++) {
                uint lightIndex = (i * iStep + iOffset);// % lightCount;
                //if (lightIndex >= lightCount) break;

                lightData = GetVoxelLight(gridIndex, lightIndex % lightCount);
                ParseLightData(lightData, lightPos, lightSize, lightRange, lightColor);

                lightColor = RGBToLinear(lightColor);

                #ifdef DYN_LIGHT_INTERLEAVE_ENABLED
                    lightColor *= iStep;
                #endif

                float traceRange2 = lightRange + 0.5;
                traceRange2 = _pow2(traceRange2);

                lightVec = lightFragPos - lightPos;
                traceDist2 = length2(lightVec);

                if (traceDist2 >= traceRange2) {
                    //i++;
                    continue;
                }

                //hasLight = true;
                //break;
            //}

            //if (!hasLight) continue;

            #if DYN_LIGHT_TYPE == LIGHT_TYPE_AREA
                float pad = 0.5 - clamp(lightSize * 0.5, 0.1, 0.4);

                vec3 lightMin = floor(lightPos + cameraOffset) - cameraOffset + pad;
                vec3 lightMax = lightMin + 1.0 - 2.0*pad;

                //vec3 diffuseLightPos = lightPos;
                vec3 diffuseLightPos = clamp(lightFragPos, lightMin, lightMax);

                //lightColor *= 2.0;
            #else
                vec3 diffuseLightPos = lightPos;
            #endif

            #if DYN_LIGHT_MODE == DYN_LIGHT_TRACED && DYN_LIGHT_TRACE_MODE == DYN_LIGHT_TRACE_DDA && DYN_LIGHT_PENUMBRA > 0 && !(defined RENDER_TRANSLUCENT || defined RENDER_COMPUTE)
                vec3 offset = GetLightPenumbraOffset() * lightSize * DynamicLightPenumbraF;

                #if DYN_LIGHT_TYPE == LIGHT_TYPE_AREA
                    diffuseLightPos = clamp(diffuseLightPos + offset, lightMin, lightMax);
                #else
                    diffuseLightPos += offset;
                #endif
            #endif

            lightVec = lightFragPos - diffuseLightPos;
            if (abs(lightVec.x) < EPSILON) lightVec.x = EPSILON;
            if (abs(lightVec.y) < EPSILON) lightVec.y = EPSILON;
            if (abs(lightVec.z) < EPSILON) lightVec.z = EPSILON;

            //lightColor = RGBToLinear(lightColor);

            uint traceFace = 1u << GetLightMaskFace(lightVec);
            if ((lightData.z & traceFace) == traceFace) continue;

            #if DYN_LIGHT_MODE == DYN_LIGHT_TRACED && defined RENDER_FRAG
                if ((lightData.z & 1u) == 1u) {
                    vec3 traceOrigin = traceEnd - lightVec;

                    if (!isSpectator) {
                    #ifdef RENDER_ENTITIES
                        if (entityId != ENTITY_PLAYER) {
                    #endif

                        #if DYN_LIGHT_PLAYER_SHADOW != PLAYER_SHADOW_NONE
                            vec3 playerPos = vec3(0.0, -0.8, 0.0);

                            #ifdef IS_IRIS
                                if (!firstPersonCamera) playerPos += eyePosition - cameraPosition;
                            #endif

                            playerPos = GetVoxelBlockPosition(playerPos);

                            vec3 playerOffset = traceOrigin - playerPos;
                            if (length2(playerOffset) < traceDist2) {
                                #if DYN_LIGHT_PLAYER_SHADOW == PLAYER_SHADOW_CYLINDER
                                    bool hit = CylinderRayTest(traceOrigin - playerPos, traceEnd - traceOrigin, 0.36, 1.0);
                                #else
                                    vec3 boundsMin = vec3(-0.36, -1.0, -0.36);
                                    vec3 boundsMax = vec3( 0.36,  1.0,  0.36);

                                    #if DYN_LIGHT_TRACE_METHOD == DYN_LIGHT_TRACE_RAY
                                        bool hit = BoxPointTest(boundsMin, boundsMax, rayStart - playerPos);
                                    #else
                                        vec3 rayInv = rcp(traceEnd - traceOrigin);
                                        bool hit = BoxRayTest(boundsMin, boundsMax, traceOrigin - playerPos, rayInv);
                                    #endif
                                #endif
                                
                                if (hit) lightColor = vec3(0.0);
                            }
                        #endif

                    #ifdef RENDER_ENTITIES
                        }
                    #endif
                    }

                    #if DYN_LIGHT_TRACE_METHOD == DYN_LIGHT_TRACE_RAY
                        lightColor *= TraceRay(traceOrigin, traceEnd, lightRange);
                    #else
                        lightColor *= TraceDDA(traceOrigin, traceEnd, lightRange);
                    #endif
                }
            #endif

            vec3 lightDir = normalize(-lightVec);
            float geoNoL = 1.0;
            if (hasGeoNormal) geoNoL = dot(localNormal, lightDir);

            float diffuseNoLm = GetLightNoL(geoNoL, texNormal, lightDir, sss);

            if (diffuseNoLm > EPSILON) {
                float lightAtt = GetLightAttenuation(lightVec, lightRange);

                vec3 lightH = normalize(lightDir + localViewDir);
                float lightLoHm = max(dot(lightDir, lightH), 0.0);

                vec3 F = vec3(0.0);
                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    float lightVoHm = max(dot(localViewDir, lightH), EPSILON);

                    //float invCosTheta = 1.0 - lightVoHm;
                    //F = f0 + (max(1.0 - roughL, f0) - f0) * pow5(invCosTheta);
                    F = F_schlickRough(lightVoHm, f0, roughL);
                #endif

                //accumDiffuse += SampleLightDiffuse(diffuseNoLm, F) * lightAtt * lightColor;
                accumDiffuse += SampleLightDiffuse(lightNoVm, diffuseNoLm, lightLoHm, roughL) * lightAtt * lightColor * (1.0 - F);

                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    #if DYN_LIGHT_TYPE == LIGHT_TYPE_AREA
                        vec3 r = reflect(-localViewDir, texNormal);
                        vec3 L = lightPos - lightFragPos;
                        vec3 centerToRay = dot(L, r) * r - L;
                        vec3 closestPoint = L + centerToRay * saturate((lightSize * 0.5) / length(centerToRay));
                        lightDir = normalize(closestPoint);
                    #endif

                    lightH = normalize(lightDir + localViewDir);

                    float lightNoLm = max(dot(texNormal, lightDir), 0.0);
                    float lightNoHm = max(dot(texNormal, lightH), EPSILON);
                    float invGeoNoL = saturate(geoNoL*40.0 + 1.0);

                    accumSpecular += invGeoNoL * SampleLightSpecular(lightNoVm, lightNoLm, lightNoHm, F, roughL) * lightAtt * lightColor;
                #endif
            }
        }

        accumDiffuse *= DynamicLightBrightness;
        accumSpecular *= DynamicLightBrightness;

        #ifdef DYN_LIGHT_FALLBACK
            // TODO: shrink to shadow bounds
            vec3 offsetPos = localPos + VoxelBlockCenter;
            float fade = minOf(min(offsetPos, VoxelBlockSize - offsetPos)) / 8.0;
            accumDiffuse = mix(blockLightDefault, accumDiffuse, saturate(fade));
            accumSpecular = mix(vec3(0.0), accumSpecular, saturate(fade));
        #endif

        #if DYN_LIGHT_TYPE == LIGHT_TYPE_AREA
            accumSpecular *= invPI;
        #endif

        blockDiffuse += accumDiffuse;
        blockSpecular += accumSpecular;
    }
    else {
        #ifdef DYN_LIGHT_FALLBACK
            blockDiffuse += blockLightDefault;
        #endif
    }
}
