/****************************************************************************
**
** Copyright (C) 2014 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt 3D Studio.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef SAMPLE_PROBE_GLSLLIB
#define SAMPLE_PROBE_GLSLLIB 1

#ifndef QSSG_ENABLE_LIGHT_PROBE_2
#define QSSG_ENABLE_LIGHT_PROBE_2 0
#endif

#ifndef QSSG_ENABLE_IBL_FOV
#define QSSG_ENABLE_IBL_FOV 0
#endif

#define USE_RGBE

uniform sampler2D lightProbe;
uniform vec4 lightProbeProperties;
uniform vec4 lightProbeRotation;
uniform vec4 lightProbeOffset;    // lightProbeOffset.w = number of mipmaps
uniform vec2 lightProbeSize;

#if QSSG_ENABLE_LIGHT_PROBE_2
uniform sampler2D lightProbe2;
uniform vec4 lightProbe2Properties;
uniform vec2 lightProbe2Size;
#endif

#if QSSG_ENABLE_IBL_FOV
uniform vec4 lightProbeOptions;
#endif

float noise1d(vec2 n)
{
    return 0.5 + 0.5 * fract(sin(dot(n.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

mat3 orthoNormalize(in mat3 tanFrame)
{
   mat3 outMat;
   outMat[0] = normalize( cross( tanFrame[1], tanFrame[2] ) );
   outMat[1] = normalize( cross( tanFrame[2], outMat[0] ) );
   outMat[2] = tanFrame[2];

   return outMat;
}

mat3 tangentFrame(vec3 N, vec3 p)
{
    // get edge vectors of the pixel triangle
    vec3 dp1 = dFdx(p);
    vec3 dp2 = dFdy(p);

    // solve the linear system
    vec3 dp2perp = cross(dp2, N);
    vec3 dp1perp = cross(N, dp1);

    vec3 T = normalize(dp1perp);
    vec3 B = normalize(dp2perp);
    return mat3(T, B, N);
}

vec2 transformSample(vec2 origUV, vec4 probeRot, vec2 probeOfs)
{
    vec2 retUV;
    retUV.x = dot( vec3(origUV, 1.0), vec3(probeRot.xy, probeOfs.x) );
    retUV.y = dot( vec3(origUV, 1.0), vec3(probeRot.zw, probeOfs.y) );
    return retUV;
}

vec3 textureProbe(sampler2D lightProbe, vec2 coord, float lod)
{
#ifdef USE_RGBE
    vec4 ret = textureLod(lightProbe, coord, lod);
    return ret.rgb * pow(2.0, ret.a * 255.0 - 128.0);
#else
    return textureLod(lightProbe, coord, lod);
#endif
}

// This is broken out into its own routine so that if we get some other
// format image than a lat-long, then we can account for that by changing
// the code here alone.
vec2 getProbeSampleUV(vec3 smpDir, vec4 probeRot, vec2 probeOfs)
{
    vec2 smpUV;

#if QSSG_ENABLE_IBL_FOV
    smpUV.x = (2.0 * atan(-smpDir.z, smpDir.x) + 3.14159265358 ) / lightProbeOptions.x;
    smpUV.y = (2.0 * atan(-smpDir.z, smpDir.y) + 3.14159265358 ) / lightProbeOptions.x;
#else
    smpUV.x = atan( smpDir.x, -smpDir.z) / 3.14159265359;
    smpUV.y = 1.0 - (acos(smpDir.y) / 1.57079632679);
#endif
    smpUV = transformSample(smpUV.xy * 0.5, probeRot, probeOfs) + vec2(0.5, 0.5);

    return smpUV;
}

vec4 getTopLayerSample(vec3 inDir, float lodShift, vec3 lodOffsets)
{
#if QSSG_ENABLE_LIGHT_PROBE_2
    if ( lightProbe2Properties.w < 0.5 )
        return vec4(0.0, 0.0, 0.0, 0.0);

    vec2 smpUV = getProbeSampleUV( inDir, vec4(1.0, 0.0, 0.0, 1.0), lightProbeProperties.xy );
    smpUV.x -= 0.5;
    smpUV.x *= lightProbe2Properties.x;
    smpUV.x += lightProbe2Properties.y;

    vec4 retVal = 0.4 * textureLod( lightProbe2, smpUV , lodShift );
    retVal += 0.2 * textureLod( lightProbe2, smpUV , lodShift+lodOffsets.x );
    retVal += 0.3 * textureLod( lightProbe2, smpUV , lodShift+lodOffsets.y );
    retVal += 0.1 * textureLod( lightProbe2, smpUV , lodShift+lodOffsets.z );
    return retVal;
#else
    return vec4(0.0, 0.0, 0.0, 0.0);
#endif
}

vec3 getProbeSample(vec3 smpDir, float lodShift, vec3 normal)
{
    vec2 smpUV = getProbeSampleUV( smpDir, lightProbeRotation, lightProbeOffset.xy );
    return textureProbe( lightProbe, smpUV , lodShift );
}

vec3 getProbeWeightedSample(vec3 smpDir, float lodShift, float roughness, vec3 normal)
{
    // This gives us a weighted sum that approximates the total filter support
    // of the full-blown convolution.
    vec2 smpUV = getProbeSampleUV(smpDir, lightProbeRotation, lightProbeOffset.xy);
    float wt = 1.0;

#if QSSG_ENABLE_IBL_FOV
    wt = min(wt, smoothstep(roughness * -0.25, roughness * 0.25, smpUV.x));
    wt = min(wt, smoothstep(roughness * -0.25, roughness * 0.25, smpUV.y));
    wt = min(wt, 1.0 - smoothstep(1.0 - roughness*0.25, 1.0 + roughness*0.25, smpUV.x));
    wt = min(wt, 1.0 - smoothstep(1.0 - roughness*0.25, 1.0 + roughness*0.25, smpUV.y));
#endif

    vec3 lodOffsets;
    lodOffsets.x = mix(-2.0, -0.70710678, roughness);
    lodOffsets.y = min(2.0 * smoothstep(0.0, 0.1, roughness), 2.0 - 1.29289 * smoothstep(0.1, 1.0, roughness));
    lodOffsets.z = min(6.0 * smoothstep(0.0, 0.1, roughness), 6.0 - 4.585786 * smoothstep(0.1, 1.0, roughness));

    ivec2 iSize = ivec2(lightProbeSize);
    vec3 ddx = dFdx(smpDir) * float(iSize.x);
    vec3 ddy = dFdy(smpDir) * float(iSize.y);

    vec2 deriv;
    deriv.x = max(dot(ddx, ddx), dot(ddy, ddy));
    deriv = clamp(deriv, vec2(1.0), vec2(iSize.x * iSize.y));
    vec2 lodBound = 0.5 * log2(deriv) - vec2(1.0);

    float minLod = lodBound.x;
    float maxLod = log2(max(float(iSize.x), float(iSize.y)));
    minLod = clamp( minLod / maxLod, 0.0, 1.0);
    minLod *= minLod * maxLod;

    lodShift = max( lodShift, minLod );

    vec3 retVal = 0.4 * textureProbe(lightProbe, smpUV , lodShift);
    retVal += 0.2 * textureProbe(lightProbe, smpUV , max(minLod, lodShift+lodOffsets.x));
    retVal += 0.3 * textureProbe(lightProbe, smpUV , lodShift+lodOffsets.y);
    retVal += 0.1 * textureProbe(lightProbe, smpUV , lodShift+lodOffsets.z);

#if QSSG_ENABLE_LIGHT_PROBE_2
    vec4 topSmp = getTopLayerSample( smpDir, lodShift, lodOffsets );
    vec3 tempVal = mix( retVal, topSmp.xyz, topSmp.w );
    retVal = mix( retVal, tempVal, lightProbe2Properties.z );
#endif

    if (lightProbeProperties.z > -1.0) {
        float ctr = 0.5 + 0.5 * lightProbeProperties.z;
        float vertWt = smoothstep(ctr-roughness*0.25, ctr+roughness*0.25, smpUV.y);
        float wtScaled = mix(1.0, vertWt, lightProbeProperties.z + 1.0);
        retVal *= wtScaled;
    }

    return retVal * wt;
}

vec3 getTopLayerSampleSimple(vec3 inDir)
{
#if QSSG_ENABLE_LIGHT_PROBE_2
    if (lightProbe2Properties.w < 0.5)
        return vec3(0.0, 0.0, 0.0);

    vec2 smpUV = getProbeSampleUV(inDir, vec4(1.0, 0.0, 0.0, 1.0), lightProbeProperties.xy);
    smpUV.x -= 0.5;
    smpUV.x *= lightProbe2Properties.x;
    smpUV.x += lightProbe2Properties.y;

    const float d = 0.761324705;
    float baseLevel = lightProbeOffset.w - 1.0;
    vec3 val = textureProbe(lightProbe, smpUV, max(baseLevel - 2.0, 0.0)) * 0.0625;
    val += textureProbe(lightProbe, smpUV, max(baseLevel - 1.0, 0.0)) * 0.25;
    val += textureProbe(lightProbe, smpUV, max(baseLevel, 0.0));
    return d * val;
#else
    return vec3(0.0, 0.0, 0.0);
#endif
}

vec4 sampleDiffuse(mat3 tanFrame)
{
    if (lightProbeProperties.w < 0.005)
        return vec4(0.0);
    vec2 smpUV = getProbeSampleUV(tanFrame[2], lightProbeRotation, lightProbeOffset.xy);
    const float d = 0.761324705; // 1.0 / 1.3125;
    float baseLevel = lightProbeOffset.w - 1.0;
    vec3 val = textureProbe(lightProbe, smpUV, max(baseLevel - 2.0, 0.0)) * 0.0625;
    val += textureProbe(lightProbe, smpUV, max(baseLevel - 1.0, 0.0)) * 0.25;
    val += textureProbe(lightProbe, smpUV, max(baseLevel, 0.0));

#if QSSG_ENABLE_LIGHT_PROBE_2
    vec3 topSmp = getTopLayerSampleSimple(smpDir);
    val = mix(val, topSmp, lightProbe2Properties.z);
#endif

    if (lightProbeProperties.z > -1.0) {
        float ctr = 0.5 + 0.5 * lightProbeProperties.z;
        float vertWt = smoothstep(ctr * 0.25, ctr + 0.25, smpUV.y);
        float wtScaled = mix(1.0, vertWt, lightProbeProperties.z + 1.0);
        val *= wtScaled;
    }

    return vec4(d * lightProbeProperties.w * val, 1.0);
}


vec4 sampleDiffuseCustomMaterial(vec3 normal, vec3 worldPos, float aoFactor)
{
    mat3 tanFrame = tangentFrame(normal, worldPos);
    return sampleDiffuse( tanFrame );
}

vec4 sampleGlossy(mat3 tanFrame, vec3 viewDir, float rough)
{
    if (lightProbeProperties.w < 0.005)
        return vec4(0.0);

    float sigma = smoothstep(0.0, 1.0, clamp(rough, 0.0001, 1.0));
    vec3 ret = vec3(0, 0, 0);

    vec3 smpDir = reflect(-viewDir, tanFrame[2]);

    // Compute the Geometric occlusion/self-shadowing term
    float NdotL = clamp(dot( smpDir, tanFrame[2]), 0.0, 0.999995);
    float k = sigma * 0.31830988618;    // roughness / pi
    float Gl = clamp((NdotL / (NdotL*(1.0-k) + k) + (1.0 - k*k)) * 0.5, 0.0, 1.0);

    vec3 outColor;

    outColor = getProbeSample(smpDir, sigma, tanFrame[2]);

    return vec4(lightProbeProperties.w * Gl * outColor, 1.0);
}

#endif
