/***********************************************/
/*           Copyright (c) 2025 Belmu          */
/*             All Rights Reserved             */
/***********************************************/

/*
    [References]:
        Taylor, M. (2019). Tone Mapping. https://64.github.io/tonemapping/
        Grossberg, D., M., & Nayar, K., S. (2004). Camera Response Functions: Database (DoRF) and Model (EMOR). https://www.cs.columbia.edu/CAVE/software/softlib/dorf.php
        Grossberg, D., M., & Nayar, K., S. (2004). Modeling the Space of Camera Response Functions. https://www1.cs.columbia.edu/CAVE/publications/pdfs/Grossberg_PAMI04.pdf
*/

uniform sampler1D cameraResponseLUT;

float sampleLUTIrradiance(int index, int component) {
    return texelFetch(cameraResponseLUT, index, 0)[component];
}

float sampleLUTIntensity(int index, int component) {
    return texelFetch(cameraResponseLUT, clamp(index + 1024, 0, 2048), 0)[component];
}

float binarySearchIndex(float value, int component) {
    int start = 0, end = 1023;

    while (start <= end) {
        int   mean  = int(floor((end + start) * 0.5));
        float guess = sampleLUTIntensity(mean, component);

             if (value == guess) return mean;
        else if (value >  guess) start = mean + 1;
        else                    end   = mean - 1;
    }
    return 0;
}

void setIntensity(inout float value, int component) {
    // Ditching automatic ISO for hardcoded values
    #if RESPONSE_CURVE == 0
        value /= 8.0;
    #elif RESPONSE_CURVE == 1
        value /= 8.0;
    #elif RESPONSE_CURVE == 2
        value /= 32.0;
    #elif RESPONSE_CURVE == 3
        value /= 8.0;
    #elif RESPONSE_CURVE == 4
        value /= 32.0;
    #elif RESPONSE_CURVE == 5
        value /= 16.0;
    #endif
    
    float upper = float(binarySearchIndex(value, component));
    int   index = int(distance(0.0, upper));

    float lowIrradiance = sampleLUTIrradiance(index, component);
    float lowIntensity  = sampleLUTIntensity (index, component);

    float highIrradiance = 1.0;
    float highIntensity  = 1.0;

    if (index < 1024) {
        highIrradiance = sampleLUTIrradiance(index + 1, component);
        highIntensity  = sampleLUTIntensity (index + 1, component);
    }

    float mixParameter = (value - lowIrradiance) / (highIrradiance - lowIrradiance);

    value = saturate(mix(lowIntensity, highIntensity, mixParameter));
}

void cameraResponse(inout vec3 color) {
    setIntensity(color.r, 0);
    setIntensity(color.g, 1);
    setIntensity(color.b, 2);
}
