/*
====================================================================================================

    Copyright (C) 2020 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

struct materialProperties {
    float roughness;
    float f0;
    bool conductor;
    bool conductorComplex;
    mat2x3 eta;
};

#define roughnessFade
const float specularMaxClamp    = sqrPi * tau;

#ifndef noBRDF
float brdfDistBeckmann(vec2 data) {
    //data.y  = max(sqr(data.y), 2e-4);
    /*data.x *= data.x;

    return rcp(pi * data.y * cos(sqr(data.x))) * (exp((data.x - 1.0) * rcp(data.y * tan(data.x))));*/

    float ndoth = data.x;
    float alpha2 = max(data.y, 2e-4);

        ndoth *= ndoth;
    float e = exp((ndoth - 1.0) / (alpha2 * tan(ndoth)));
    float num = rcp(pi * alpha2 * cos(ndoth * ndoth));
    return num*e;
}
float brdfDistTrowbridgeReitz(vec2 data) {
    data.x *= data.x;
    data.y *= data.y;

    return max(data.y, 1e-5) * rcp(max(pi * sqr(data.x * (data.y - 1.0) + 1.0), 1e-10));
}
float brdfGeometrySchlick(vec2 data) {  //y = sqr(roughness + 1) / 8.0
    return data.x * rcp(data.x * (1.0 - data.y) + data.y);
}
float brdfGeometryBeckmann(vec2 data) {
    float c     = data.x * rcp(data.y * sqrt(1.0 - sqr(data.x)));

    if (c >= 1.6) return 1.0;
    else return (3.535 * c + 2.181 * sqr(c)) * rcp(1.0 + 2.276 * c + 2.577 * sqr(c));
}

float brdfShadowSmithBeckmann(float nDotV, float nDotL, float roughness) {
    return brdfGeometryBeckmann(vec2(nDotL, roughness)) * brdfGeometryBeckmann(vec2(nDotV, roughness));
}
float brdfShadowSmithSchlick(float nDotV, float nDotL, float roughness) {
    roughness   = sqr(roughness + 1.0) / 8.0;
    return brdfGeometrySchlick(vec2(nDotL, roughness)) * brdfGeometrySchlick(vec2(nDotV, roughness));
}
#endif

float fresnelDielectric(vec2 data) {
    data.y  = min(sqrt(data.y), 0.99999);
    data.y  = (1.0 + data.y) * rcp(1.0 - data.y);

    float sinThetaI     = sqrt(saturate(1.0 - sqr(data.x)));
    float sinThetaT     = sinThetaI * rcp(max(data.y, 1e-16));
    float cosThetaT     = sqrt(1.0 - sqr(sinThetaT));

    float Rs    = sqr((data.x - (data.y * cosThetaT)) * rcp(max(data.x + (data.y * cosThetaT), 1e-10)));
    float Rp    = sqr((cosThetaT - (data.y * data.x)) * rcp(max(cosThetaT + (data.y * data.x), 1e-10)));

    return saturate((Rs + Rp) * 0.5);
}
vec3 fresnelConductor(float cosTheta, mat2x3 data) {
    vec3 eta            = data[0];
    vec3 etak           = data[1];
    float cosTheta2     = sqr(cosTheta);
    float sinTheta2     = 1.0 - cosTheta2;
    vec3 eta2           = sqr(eta);
    vec3 etak2          = sqr(etak);

    vec3 t0             = eta2 - etak2 - sinTheta2;
    vec3 a2plusb2       = sqrt(sqr(t0) + 4.0 * eta2 * etak2);
    vec3 t1             = a2plusb2 + cosTheta2;
    vec3 a              = sqrt(0.5 * (a2plusb2 + t0));
    vec3 t2             = 2.0 * a * cosTheta;
    vec3 Rs             = (t1 - t2) * rcp(max(t1 + t2, 1e-16));

    vec3 t3             = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2;
    vec3 t4             = t2 * sinTheta2;   
    vec3 Rp             = Rs * (t3 - t4) * rcp(max(t3 + t4, 1e-16));

    return saturate((Rs + Rp) * 0.5);
}
vec3 fresnelTinted(float cosTheta, vec3 tint) {
    vec3 tintSqrt   = sqrt(clamp(tint, 0.0, 0.99));
    vec3 n          = (1.0 + tintSqrt) / (1.0 - tintSqrt);
    vec3 g          = sqrt(sqr(n) + sqr(cosTheta) - 1);

    return 0.5 * sqr((g - cosTheta) / (g + cosTheta)) * (1.0 + sqr(((g + cosTheta) * cosTheta - 1.0) / ((g - cosTheta) * cosTheta + 1.0)));
}

float fresnelSchlick(float f0, float VoH) {
    return saturate(f0 + (1.0 - f0) * pow5(1.0 - VoH));
}
float fresnelSchlickInverse(float f0, float VoH) {
    return 1.0 - saturate(f0 + (1.0 - f0) * pow5(1.0 - VoH));
}
float fresnelSchlick(float f0, float f90, float VoH) {
    return saturate(f0 + ((f90 - f0) * pow5(1.0 - VoH)));
}

#ifndef noBRDF
float diffuseBurley(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) {
    /*
        Based on Unreal Engine 4 and Filament
    */

    vec3 halfWay    = normalize(viewDir + lightDir);
    float nDotL     = max0(dot(normal, lightDir));
    //float nDotH     = max0(dot(normal, halfWay));
    float nDotV     = max0(dot(normal, viewDir));
    float vDotH     = max0(dot(viewDir, halfWay));
    float lDotH     = max0(dot(lightDir, halfWay));

    float f90       = 0.5 + 2.0 * sqr(lDotH) * roughness;
    float lightScatter  = fresnelSchlick(1.0, f90, nDotL);
    float viewScatter   = fresnelSchlick(1.0, f90, nDotV);

    return lightScatter * viewScatter * rpi;
}
float diffuseHammon(vec3 normal, vec3 viewDir, vec3 lightDir, float roughness) {
    float nDotL     = max0(dot(normal, lightDir));

    if (nDotL <= 0.0) return (0.0);

    float nDotV     = max0(dot(normal, viewDir));
    //if (nDotV < 0.0) return mix(0.0, nDotL * rpi, cubeSmooth(saturate(-nDotV * pi)));

    float lDotV     = max0(dot(lightDir, viewDir));

    vec3 halfWay    = normalize(viewDir + lightDir);
    float nDotH     = max0(dot(normal, halfWay));

    float facing    = lDotV * 0.5 + 0.5;

    float singleRough = facing * (0.9 - 0.4 * facing) * ((0.5 + nDotH) * rcp(max(nDotH, 0.02)));
    float singleSmooth = 1.05 * fresnelSchlickInverse(0.0, nDotL) * fresnelSchlickInverse(0.0, max0(nDotV));

    float single    = saturate(mix(singleSmooth, singleRough, roughness) * rpi);
    float multi     = 0.1159 * roughness;

    return saturate((multi + single) * nDotL);
}

vec3 BRDF(vec3 viewDir, vec3 lightDir, vec3 normal, materialProperties material) {
    vec3 halfWay    = normalize(viewDir + lightDir);

    float nDotL     = max0(dot(normal, lightDir));
    float nDotH     = max0(dot(normal, halfWay));
    float vDotN     = max0(dot(viewDir, normal));
    float vDotH     = max0(dot(viewDir, halfWay));

    vec2 dataD      = vec2(nDotH, material.roughness);

    float D         = 0.0;
    float G         = 0.0;
    float result    = 0.0;

    if (material.conductor) {
        D       = brdfDistTrowbridgeReitz(dataD);
        G       = brdfShadowSmithSchlick(vDotN, nDotL, material.roughness);

        result  = max0(D * G * rcp(4.0 * vDotN * nDotL));

        return vec3(result) * fresnelConductor(vDotH, material.eta);
    } else {
        #if 1
            D       = brdfDistTrowbridgeReitz(dataD);
            G       = brdfShadowSmithSchlick(vDotN, nDotL, material.roughness);

            result  = max0(D * G * rcp(max(4.0 * vDotN * nDotL, 1e-10)));
            //result  = min(result, specularMaxClamp);

            return vec3(result * fresnelDielectric(vec2(vDotH, material.f0)));
        #else
            D       = brdfDistBeckmann(dataD);
            G       = brdfShadowSmithBeckmann(vDotN, nDotL, material.roughness);

            result  = max0(D * G * rcp(4.0 * vDotN * nDotL));

            #ifdef roughnessFade
            result *= 1.0 - sstep(material.roughness, 0.8, 1.0) * 0.9;
            #endif

            return vec3(result * fresnelDielectric(vec2(vDotH, material.f0)));
        #endif
    }
}

vec3 BRDF(vec3 viewDir, vec3 lightDir, vec3 normal, materialProperties material, vec3 albedo) {
    vec3 halfWay    = normalize(viewDir + lightDir);

    float nDotL     = max0(dot(normal, lightDir));
    float nDotH     = max0(dot(normal, halfWay));
    float vDotN     = max0(dot(viewDir, normal));
    float vDotH     = max0(dot(viewDir, halfWay));

    vec2 dataD      = vec2(nDotH, material.roughness);

    float D         = 0.0;
    float G         = 0.0;
    float result    = 0.0;

    if (material.conductor) {
        D       = brdfDistTrowbridgeReitz(dataD);
        G       = brdfShadowSmithSchlick(vDotN, nDotL, material.roughness);

        result  = max0(D * G * rcp(max(4.0 * vDotN * nDotL, 1e-10)));

        vec3 fresnel = material.conductorComplex ? fresnelConductor(vDotH, material.eta) : fresnelTinted(vDotH, albedo);

        return vec3(result) * fresnel;
    } else {
        #if 1
            D       = brdfDistTrowbridgeReitz(dataD);
            G       = brdfShadowSmithSchlick(vDotN, nDotL, material.roughness);

            result  = max0(D * G * rcp(max(4.0 * vDotN * nDotL, 1e-10)));
            //result  = min(result, specularMaxClamp);

            return vec3(result * fresnelDielectric(vec2(vDotH, material.f0)));
        #else
            D       = brdfDistBeckmann(dataD);
            G       = brdfShadowSmithBeckmann(vDotN, nDotL, material.roughness);

            result  = max0(D * G * rcp(4.0 * vDotN * nDotL));

            #ifdef roughnessFade
            result *= 1.0 - sstep(material.roughness, 0.8, 1.0) * 0.9;
            #endif

            return vec3(result * fresnelDielectric(vec2(vDotH, material.f0)));
        #endif
    }
}

vec3 BRDF_Beckmann(vec3 viewDir, vec3 lightDir, vec3 normal, materialProperties material) {
    vec3 halfWay    = normalize(viewDir + lightDir);

    float nDotL     = max0(dot(normal, lightDir));
    float nDotH     = max0(dot(normal, halfWay));
    float vDotN     = max0(dot(viewDir, normal));
    float vDotH     = max0(dot(viewDir, halfWay));

    vec2 dataD      = vec2(nDotH, material.roughness);

    float D         = 0.0;
    float G         = 0.0;
    float result    = 0.0;

    D       = brdfDistBeckmann(dataD);
    G       = brdfShadowSmithBeckmann(vDotN, nDotL, material.roughness);

    result  = max0(D * G * rcp(4.0 * vDotN * nDotL));
    result  = min(result, specularMaxClamp);

    #ifdef roughnessFade
    result *= 1.0 - sstep(material.roughness, 0.8, 1.0) * 0.9;
    #endif

    return vec3(result * fresnelDielectric(vec2(vDotH, material.f0)));
}
#endif

vec3 BRDFfresnel(vec3 viewDir, vec3 normal, materialProperties material, vec3 albedo) {
    float vDotN     = max0(dot(viewDir, normal));

    if (material.conductor) {
        return material.conductorComplex ? fresnelConductor(vDotN, material.eta) : fresnelTinted(vDotN, albedo);
    } else {
        return vec3(fresnelDielectric(vec2(vDotN, material.f0)));
    }
}
vec3 BRDFfresnel(vec3 viewDir, vec3 normal, materialProperties material) {
    float vDotN     = max0(dot(viewDir, normal));
    return vec3(fresnelDielectric(vec2(vDotN, material.f0)));
}