-- glslfx version 0.1

//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

--- This is what an import might look like.
--- #import $TOOLS/glf/shaders/simpleLighting.glslfx

-- glsl SimpleLighting.GeometryInjection
#ifndef NUM_SHADOWS
#define NUM_SHADOWS 0
#endif

#if NUM_SHADOWS == 0

#else  // NUM_SHADOWS == 0

struct ShadowMatrix {
    mat4 eyeToShadowMatrix;
    mat4 shadowToEyeMatrix;
    float blur;
    float bias;
    float padding0;
    float padding1;
};

FORWARD_DECL(mat4 GetWorldToViewMatrix());
FORWARD_DECL(mat4 GetWorldToViewInverseMatrix());

ShadowMatrix GetShadow(int shadowIndex) {
    ShadowMatrix shadowMat;
    shadowMat.eyeToShadowMatrix = HdGet_shadow_worldToShadowMatrix(shadowIndex) 
        * GetWorldToViewInverseMatrix();
    shadowMat.shadowToEyeMatrix = GetWorldToViewMatrix() * 
        HdGet_shadow_shadowToWorldMatrix(shadowIndex);
    shadowMat.blur = HdGet_shadow_blur(shadowIndex);
    shadowMat.bias = HdGet_shadow_bias(shadowIndex);
    return shadowMat;
}

out vec2 FshadowFilterWidth[NUM_SHADOWS];

// Transforms a unit tangent vector parallel to a coordinate axis in
// shadow space to eye space and returns the length of the result
// (without doing the perspective division).
//
// The first argument is assumed to be a column of the shadow to eye
// matrix (using first column corresponds to unit tangent vector
// pointing in x-direction, ...). The second argument is supposed to
// be the (homogeneous) coordinates of the point in eye space.
//
float
shadowTangentLength(vec4 dir, vec4 Peye)
{
    // Computation similar to computeRayDirectionEye in volume.glslfx.
    const vec3 shadowTangent = dir.xyz * Peye.w - dir.w * Peye.xyz;
    return length(shadowTangent);
}

// Computes the lengths that tangent vectors parallel to the x-,
// respectively, y-direction in shadow space at the current location
// need to have such that their image in eye space have length blur
// (without perspective division).
//
void
computeShadowFilterWidth(vec4 Peye)
{
    // interpolate filter width
    for (int i = 0; i < NUM_SHADOWS; ++i) {
        ShadowMatrix shadowMat = GetShadow(i);
        FShadowFilterWidth[i] =
            vec2(shadowMat.blur) /
            vec2(shadowTangentLength(shadowMat.shadowToEyeMatrix[0], Peye),
                 shadowTangentLength(shadowMat.shadowToEyeMatrix[1], Peye));
    }
}

#define COMPUTE_SHADOW_FILTER_WIDTH 1

#endif  // NUM_SHADOWS == 0

-- glsl SimpleLighting.LightIntegrator

// ---------------------------------------------------------------------------
// struct definitions
// ---------------------------------------------------------------------------
#ifndef NUM_LIGHTS
#define NUM_LIGHTS 0
#endif
#ifndef NUM_SHADOWS
#define NUM_SHADOWS 0
#endif

#ifndef HD_HAS_integrateLights
#define HD_HAS_integrateLights
#endif

struct LightSource {
    vec4 position;
    vec4 ambient;
    vec4 diffuse;
    vec4 specular;
    vec4 spotDirection;
    vec4 spotCutoffAndFalloff;
    vec4 attenuation;
    mat4 worldToLightTransform;
    int shadowIndexStart;
    int shadowIndexEnd;
    bool hasShadow;
    bool isIndirectLight;
};

struct ShadowMatrix {
    mat4 eyeToShadowMatrix;
    mat4 shadowToEyeMatrix;
    float blur;
    float bias;
    float padding0;
    float padding1;
};

struct LightingContribution {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct LightingInterfaceProperties {
    float shininess;
    float roughness;
    float metallic;
    vec3 matSpecular;
};

// ---------------------------------------------------------------------------
// data accessors
// ---------------------------------------------------------------------------
#if NUM_LIGHTS == 0

#else // NUM_LIGHTS == 0

bool GetUseLighting() {
    return HdGet_useLighting();
}

bool GetUseColorMaterialDiffuse() {
    return HdGet_useColorMaterialDiffuse();
}

FORWARD_DECL(mat4 GetWorldToViewMatrix());
FORWARD_DECL(mat4 GetWorldToViewInverseMatrix());

LightSource GetLightSource(int lightIndex) {
    LightSource light;
    light.position = GetWorldToViewMatrix() * 
        HdGet_lightSource_position(lightIndex);
    light.ambient = HdGet_lightSource_ambient(lightIndex);
    light.diffuse = HdGet_lightSource_diffuse(lightIndex);
    light.specular = HdGet_lightSource_specular(lightIndex);
    light.spotDirection = GetWorldToViewMatrix() * 
        vec4(HdGet_lightSource_spotDirection(lightIndex), 0);
    light.spotCutoffAndFalloff = vec4(HdGet_lightSource_spotCutoff(lightIndex),
        HdGet_lightSource_spotFalloff(lightIndex), 0, 0);
    light.attenuation = vec4(HdGet_lightSource_attenuation(lightIndex), 0);
    light.worldToLightTransform = 
        HdGet_lightSource_worldToLightTransform(lightIndex);
    light.shadowIndexStart = HdGet_lightSource_shadowIndexStart(lightIndex);
    light.shadowIndexEnd = HdGet_lightSource_shadowIndexEnd(lightIndex);
    light.hasShadow = HdGet_lightSource_hasShadow(lightIndex);
    light.isIndirectLight = HdGet_lightSource_isIndirectLight(lightIndex);
    return light;
}
#endif // NUM_LIGHTS == 0

#if NUM_SHADOWS == 0

#else // NUM_SHADOWS == 0

ShadowMatrix GetShadow(int shadowIndex) {
    ShadowMatrix shadowMat;
    shadowMat.eyeToShadowMatrix = HdGet_shadow_worldToShadowMatrix(shadowIndex) 
        * GetWorldToViewInverseMatrix();
    shadowMat.shadowToEyeMatrix = GetWorldToViewMatrix() * 
        HdGet_shadow_shadowToWorldMatrix(shadowIndex);
    shadowMat.blur = HdGet_shadow_blur(shadowIndex);
    shadowMat.bias = HdGet_shadow_bias(shadowIndex);
    return shadowMat;
}

float
shadowTangentLength(vec4 dir, vec4 Peye)
{
    const vec3 shadowTangent = dir.xyz * Peye.w - dir.w * Peye.xyz;
    return length(shadowTangent);
}

vec2
computeShadowFilterWidth(int index, vec4 Peye)
{
    ShadowMatrix shadowMat = GetShadow(index);
    return
        vec2(shadowMat.blur) /
        vec2(shadowTangentLength(shadowMat.shadowToEyeMatrix[0], Peye),
             shadowTangentLength(shadowMat.shadowToEyeMatrix[1], Peye));
}

#endif  // NUM_SHADOWS == 0

// ---------------------------------------------------------------------------
// lighting functions
// ---------------------------------------------------------------------------

#if NUM_LIGHTS == 0

LightingContribution
integrateLightsConstant(vec4 Peye, vec3 Neye, LightingInterfaceProperties props)
{
    LightingContribution result;
    result.ambient = vec3(0.0);
    //pefectly diffuse white hemisphere contribution
    result.diffuse = vec3(1.0);
    result.specular = vec3(0.0);

    return result;
}

LightingContribution
integrateLightsDefault(vec4 Peye, vec3 Neye, LightingInterfaceProperties props)
{  
    // If no lights, lighting contribution is zero.
    LightingContribution result;
    result.ambient = vec3(0.0);
    result.diffuse = vec3(0.0);
    result.specular = vec3(0.0);

    return result;
}

#else // NUM_LIGHTS == 0

#if NUM_SHADOWS > 0
float
shadowCompare(int shadowIndex, vec4 coord)
{
    coord /= coord.w;
    coord.z = min(1.0, coord.z + GetShadow(shadowIndex).bias);

    // Cascade selection (in shadowing()) ensures Pshadow.xy is in [0,1],
    // but shadowFilter() can throw that off so we need to clamp.
    // XXX: sample the next cascade over?
    coord.xy = clamp(coord.xy, vec2(0), vec2(1));

    return HdGet_shadowCompareTextures(shadowIndex, coord.xyz).x;
}

#ifndef SHADOW_FILTER
float
shadowFilter(int shadowIndex, vec4 coord, vec4 Peye)
{
    return shadowCompare(shadowIndex, coord);
}
#endif // SHADOW_FILTER
#endif // NUM_SHADOWS > 0

float
shadowing(int lightIndex, vec4 Peye)
{
#if NUM_SHADOWS > 0
    for (int shadowIndex = GetLightSource(lightIndex).shadowIndexStart;
         shadowIndex <= GetLightSource(lightIndex).shadowIndexEnd;
         ++shadowIndex) {

        vec4 coord = GetShadow(shadowIndex).eyeToShadowMatrix * Peye;

        // If the point-to-be-lit isn't covered by this cascade, skip to
        // the next one...
        if (any(lessThan(coord.xyz, vec3(0))) ||
            any(greaterThan(coord.xyz, coord.www))) {
            continue;
        }

        return shadowFilter(shadowIndex, coord, Peye);
    }
#endif // NUM_SHADOWS > 0
    // ... either no shadows, or no coverage.
    return 1.0;
}

float
lightDistanceAttenuation(vec4 Peye, int index)
{
    float distAtten = 1.0;
    LightSource light = GetLightSource(index);
    if (light.position.w != 0.0) {
        const float d = distance(light.position, Peye);
        const float atten = light.attenuation[0] +
                            light.attenuation[1] * d +
                            light.attenuation[2] * d*d;
        if (atten == 0) {
            distAtten = 0.0;
        } else {
            distAtten = 1.0 / atten;
        }
    }
    return distAtten;
}

float
lightSpotAttenuation(vec3 l, int index)
{
    float spotAtten = 1.0;
    LightSource light = GetLightSource(index);
    if (light.spotCutoffAndFalloff.x < 180.0) {
        float cosLight = dot(-l, light.spotDirection.xyz);
        spotAtten = (cosLight < cos(radians(light.spotCutoffAndFalloff.x)))
                ? 0.0 : pow(cosLight, light.spotCutoffAndFalloff.y);
    }
    return spotAtten;
}

vec2
projectSphericalToLatLong(vec3 sample3D)
{
    // project spherical coord onto latitude-longitude map with
    // latitude: +y == pi/2 and longitude: +z == 0, +x == pi/2
    const float PI = 3.1415;
    vec2 coord = vec2((atan(sample3D.z, sample3D.x) + 0.5 * PI) / (2.0 * PI),
                      acos(sample3D.y) / PI);
    return coord;
}

float
schlickFresnel(float EdotH)
{
    return pow(max(0.0, 1.0 - EdotH), 5.0);
}

LightingContribution
evaluateIndirectLight(vec3 Neye, vec3 Reye, float NdotE, float EdotH, 
    LightingInterfaceProperties props, int i)
{
    LightingContribution result;
    result.diffuse = vec3(0);
    result.specular = vec3(0);
    result.ambient = vec3(0);

#ifdef HD_HAS_domeLightIrradiance
    const vec3 matSpecular = props.matSpecular;
    // Assumes IoR = 1.5
    const vec3 F0 = mix(0.04 * matSpecular, matSpecular, props.metallic);
    const vec3 F90 = mix(props.matSpecular, vec3(1), props.metallic);
    const float fresnel = schlickFresnel(EdotH);
    const vec3 F = mix(F0, F90, fresnel);

    mat4 transformationMatrix = 
        GetLightSource(i).worldToLightTransform * GetWorldToViewInverseMatrix();

    // Diffuse Component
    vec3 dir = normalize((transformationMatrix * vec4(Neye,0.0)).xyz);
    vec2 coord = projectSphericalToLatLong(dir);
    vec3 diffuse = HdGet_domeLightIrradiance(coord).rgb;

    // Specular Component 
    const float MAX_REFLECTION_LOD = 
        textureQueryLevels(HdGetSampler_domeLightPrefilter());
    const float roughness = props.roughness;
    const float lod = roughness * MAX_REFLECTION_LOD;
    vec3 Rdir = normalize((transformationMatrix * vec4(Reye,0.0)).xyz);
    vec2 Rcoord = projectSphericalToLatLong(Rdir);
    vec3 prefilter = HdTextureLod_domeLightPrefilter(Rcoord, lod).rgb;
    vec2 brdf = HdGet_domeLightBRDF(vec2(NdotE, roughness)).rg;
    vec3 specular = prefilter * (F * brdf.x + brdf.y);

    result.diffuse = diffuse;
    result.specular = specular;
#else
    result.diffuse =  vec3(1, 1, 1);
#endif

    return result;
}

// for the compatibility, turn on shadowing by default.
#ifndef USE_SHADOWS
#define USE_SHADOWS 1
#endif

LightingContribution
integrateLightsDefault(vec4 Peye, vec3 Neye, LightingInterfaceProperties props)
{
    LightingContribution result;
    result.ambient = vec3(0);
    result.diffuse = vec3(0);
    result.specular = vec3(0);
    
    vec3 n = normalize(Neye);
    vec3 e = normalize(-Peye.xyz);

    float shininess = props.shininess;

    for (int i = 0; i < NUM_LIGHTS; ++i) {
        LightSource light = GetLightSource(i);

        vec4 Plight = (light.isIndirectLight)
                        ? vec4(0,0,0,1)
                        : light.position;
        vec3 l = (Plight.w == 0.0)
                        ? normalize(Plight.xyz)
                        : normalize(Plight - Peye).xyz;
        vec3 h = normalize(l + vec3(0,0,1));    // directional viewer

        if (light.isIndirectLight) {
            float NdotE = max(0.0, dot(n, e));
            float EdotH = max(0.0, dot(e, h));
            vec3 Reye = reflect(-e, n);

            LightingContribution indirectLight = evaluateIndirectLight(
                Neye, Reye, NdotE, EdotH, props, i);

            result.ambient += light.ambient.rgb;
            result.diffuse += indirectLight.diffuse * light.diffuse.rgb;
            result.specular += indirectLight.specular * light.specular.rgb;
        } else {
            //cosine of incident angle of light
            float NdotL = max(0.0, dot(n, l));
            
            //cosine of incident angle from halfway vector between the eye and the light
            float NdotH = max(0.0, dot(n, h));

            //Lambert
            float d = NdotL;
            
            //Blinn-Phong
            float s = pow(NdotH, shininess);

            float atten = lightDistanceAttenuation(Peye, i);
            atten *= lightSpotAttenuation(l, i);

#if USE_SHADOWS
            float shadow = light.hasShadow ?
                shadowing(/*lightIndex=*/i, Peye) : 1.0;
#else
            float shadow = 1.0;
#endif
            result.ambient += atten * light.ambient.rgb;
            result.diffuse += atten * shadow * d * light.diffuse.rgb;
            result.specular += atten * shadow * s * light.specular.rgb;
        }
    }

    return result;
}

LightingContribution
integrateLightsConstant(vec4 Peye, vec3 Neye, LightingInterfaceProperties props)
{
    LightingContribution result;
    result.ambient = vec3(0);
    result.specular = vec3(0);
    result.diffuse = vec3(1);

    return result;
}

#endif // NUM_LIGHTS == 0

-- glsl SimpleLighting.SimpleLighting

// ---------------------------------------------------------------------------
// lighting functions
// ---------------------------------------------------------------------------

FORWARD_DECL(LightingContribution integrateLights(vec4 Peye, vec3 Neye, 
    LightingInterfaceProperties props));

vec4
simpleLighting(vec4 color, vec4 Peye, vec3 Neye, vec4 Ctint,
               vec4 matDiffuse, vec4 matAmbient, vec4 matSpecular, float matShininess)
{
    LightingInterfaceProperties props;

    // Get the roughness and metallic values

#if defined(HD_HAS_displayMetallic)
    const float metallic  = max(0.0, min(1.0, float(HdGet_displayMetallic())));
#else
    const float metallic = 0.0;
#endif
    props.metallic = metallic;

#if defined(HD_HAS_displayRoughness)
    const float roughness = max(0.0, min(1.0, float(HdGet_displayRoughness())));
    const float specularExp = (1.0 - roughness) * 120.0 + 8.0;
    props.shininess = specularExp;
    matSpecular.rgb = mix(vec3(1.0), matDiffuse.rgb, metallic);
#else
    const float roughness = 0.0;
    props.shininess = matShininess;
#endif
    props.roughness = roughness;
    props.matSpecular = matSpecular.rgb;

    LightingContribution light = integrateLights(Peye, Neye, props);

    // determine the specular and diffuse intensity
    const float Ks = (1.0 - roughness) + 2.0 * metallic;

    color.rgb += light.ambient * matAmbient.rgb;
    color.rgb += Ctint.rgb * light.diffuse * matDiffuse.rgb;
    color.rgb += Ks * light.specular * matSpecular.rgb;

    color.a = matDiffuse.a;

    return color;
}

vec4
simpleLightingMaterial(vec4 color, vec4 Peye, vec3 Neye, vec4 Ctint)
{
    // XXX todo: useColorMaterialDiffuse

    vec4 diffuse = color;
    vec4 ambient = material.ambient;
    vec4 specular = material.specular;
    float shininess = material.shininess;

    color = material.emission + (material.sceneColor * ambient);

    return simpleLighting(color, Peye, Neye, Ctint,
                          diffuse, ambient, specular, shininess);
}

vec4
simpleLighting(vec4 color, vec4 Peye, vec3 Neye, vec4 Ctint)
{
    return simpleLightingMaterial(color, Peye, Neye, Ctint);
}
