/****************************************************************************
**
** 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_AREA_GLSLLIB
#define SAMPLE_AREA_GLSLLIB 1

#include "funcareaLightVars.glsllib"
#include "funccalculateDiffuseAreaOld.glsllib"
#include "funcsampleAreaGlossyDefault.glsllib"

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

vec3 quatRotate( vec4 q, vec3 v )
{
    return v + 2.0 * cross( cross( v, q.xyz ) + q.w * v, q.xyz );
}

vec3 lightUVToRay( in vec3 pos, in vec3 lightPos, in mat3 lightFrame, in float width, in float height, in vec2 uv )
{
    width *= dot(lightFrame[0], lightFrame[0]);
    height *= dot(lightFrame[1], lightFrame[1]);

    vec3 hitPos = lightPos + (uv.x - 0.5) * width * lightFrame[0] + (uv.y - 0.5) * height * lightFrame[1];
    return normalize(hitPos - pos);
}

// This is usable only for planar rectangular lights.  Will need to break this down into different paths for different shapes.
float getUVHitBounds( in vec3 pos, in mat3 lightFrame, in vec3 lightPos, in float width, in float height, in vec3 V0, in vec3 V1, in vec3 V2, in vec3 V3, out vec2 UVmin, out vec2 UVmax )
{
    float d0 = dot( lightPos, lightFrame[2] );
    float d1 = dot( pos, lightFrame[2] );

    // If the point is behind the light, it can't be illuminated.
    if (d0 < d1)
    {
        UVmin = vec2( -1.0, -1.0 );
        UVmax = vec2( -1.0, -1.0 );
        return 0.0;
    }

    // Compute where all the rays of the light beam hit the light plane
    float t[4];
    vec3 v[4];

    v[0] = V0;  v[1] = V1;  v[2] = V2;  v[3] = V3;
    t[0] = (d0 - d1) / dot(V0, lightFrame[2]);
    t[1] = (d0 - d1) / dot(V1, lightFrame[2]);
    t[2] = (d0 - d1) / dot(V2, lightFrame[2]);
    t[3] = (d0 - d1) / dot(V3, lightFrame[2]);

    UVmin = vec2(1e6, 1e6);
    UVmax = vec2(-1e6, -1e6);

    // If any of them are "negative" relative to the ray direction
    // We don't want to consider them.
    bool weight[4];
    float wtsum = 0.0;
    weight[0] = (t[0] > 0.0);
    weight[1] = (t[1] > 0.0);
    weight[2] = (t[2] > 0.0);
    weight[3] = (t[3] > 0.0);

    width *= dot(lightFrame[0], lightFrame[0]);
    height *= dot(lightFrame[1], lightFrame[1]);

    for (int i = 0; i < 4; ++i)
    {
        vec3 curPos = pos + v[i] * t[i];
        curPos -= lightPos;
        vec2 curUV = vec2( dot(curPos, lightFrame[0]) / width, dot(curPos, lightFrame[1]) / height ) + vec2(0.5);

        UVmin = min(UVmin, curUV);   UVmax = max(UVmax, curUV);
        wtsum += weight[i] ? 0.25 : 0.0;
    }

    return wtsum;
}

// Shooting a narrow beam, and then scaling up that beam based on the actual roughness
vec4 sampleAreaGlossy( in mat3 tanFrame, in vec3 pos, in int lightIdx, in vec3 viewDir, in float roughU, in float roughV )
{
    float sigmaU = clamp( 0.5 * roughU, 0.005, 0.5 );
    float sigmaV = clamp( 0.5 * roughV, 0.005, 0.5 );
    vec2 UVset[5];

    mat3 lightFrame = mat3( arealights[lightIdx].right.xyz, arealights[lightIdx].up.xyz, -arealights[lightIdx].direction.xyz );

    float thetaI = acos( dot(viewDir, lightFrame[2]) );
    vec2 minMaxThetaH = vec2( (thetaI - 1.5707) * 0.5,
                      (thetaI + 1.5707) * 0.5 );
    vec4 sinCosThetaH = vec4( abs(sin(minMaxThetaH)), abs(cos(minMaxThetaH)) );

    // First thing we do is compute a small-scale version of the ray hit for a very tiny roughness
    // then we scale that up based on the _actual_ roughness.
    float wt = computeMicroHit( pos, tanFrame, arealights[lightIdx].position.xyz, lightFrame,
                                arealights[lightIdx].right.w, arealights[lightIdx].up.w, viewDir, UVset );
    UVset[0] -= UVset[4];  UVset[1] -= UVset[4];
    UVset[2] -= UVset[4];  UVset[3] -= UVset[4];

    UVset[0] *= mix(1.0, sinCosThetaH.y / 0.005, sigmaU);  UVset[1] *= mix(1.0, sinCosThetaH.x / 0.005, sigmaU);
    UVset[2] *= mix(1.0, sinCosThetaH.y / 0.005, sigmaV);  UVset[3] *= mix(1.0, sinCosThetaH.x / 0.005, sigmaV);

    UVset[0] += UVset[4];  UVset[1] += UVset[4];
    UVset[2] += UVset[4];  UVset[3] += UVset[4];

    vec2 UVmin = UVset[4], UVmax = UVset[4];
    vec2 cminUV, cmaxUV;
    UVmin = min(UVmin, UVset[0]);   UVmax = max(UVmax, UVset[0]);
    UVmin = min(UVmin, UVset[1]);   UVmax = max(UVmax, UVset[1]);
    UVmin = min(UVmin, UVset[2]);   UVmax = max(UVmax, UVset[2]);
    UVmin = min(UVmin, UVset[3]);   UVmax = max(UVmax, UVset[3]);

    cminUV = clamp( UVmin, vec2(0.0), vec2(1.0) );
    cmaxUV = clamp( UVmax, vec2(0.0), vec2(1.0) );

    vec2 hitScale = (cmaxUV - cminUV);
    vec2 fullScale = (UVmax - UVmin);
    float intensity = ( hitScale.x * hitScale.y ) / max( fullScale.x * fullScale.y, 0.0001 );

    return vec4( wt * intensity );
}

vec4 sampleAreaDiffuse( in mat3 tanFrame, in vec3 pos, in int lightIdx )
{
    float intensity = 0.0;
    vec3 finalDir;
    intensity = calculateDiffuseAreaOld( arealights[lightIdx].direction.xyz, arealights[lightIdx].position.xyz,
                        arealights[lightIdx].up, arealights[lightIdx].right, pos, finalDir );
    intensity *= clamp( dot(finalDir, tanFrame[2]), 0.0, 1.0 );
    return vec4( intensity );
}

vec4 sampleAreaDiffuseTransmissive( in mat3 tanFrame, in vec3 pos, in int lightIdx, in vec4 transmissiveColor, in float translucentFalloff, float lightWrap )
{
    float intensity = 0.0;
    vec3 finalDir;
    intensity = calculateDiffuseAreaOld( arealights[lightIdx].direction.xyz, arealights[lightIdx].position.xyz,
              arealights[lightIdx].up, arealights[lightIdx].right, pos, finalDir );
    intensity *= clamp( dot(finalDir, -tanFrame[2]), 0.0, 1.0 );

    float l = 0.2126 * transmissiveColor.r + 0.7152 * transmissiveColor.g + 0.0722 * transmissiveColor.b;

    float I = max( 0.0, ((dot(finalDir, -tanFrame[2]) + lightWrap)/ (1.0 + lightWrap)) );
    float translucent_thickness =  l * l;
    float translucent_thickness_exp = exp( translucent_thickness * translucentFalloff) * I;

    return vec4( translucent_thickness_exp * intensity );
}

#endif
