#version 450

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec3 inColor;

layout(location = 0) out vec4 outColor;

layout (push_constant) uniform PushConstants {
    mat4 mvpTransform;
    mat4 mvpTransformIT;
};

#include "ubo_perframe.glsl"

#include "common.glsl"
#include "noise.glsl"
#include "lighting.glsl"

// Calculate the surface normal using screen-space partial derivatives of the height field
vec3 CalculateSurfaceNormal(vec3 position, vec3 normal, float height)
{
    vec3 dpdx = dFdx(position);
    vec3 dpdy = dFdy(position);
 
    float dhdx = dFdx(height);
    float dhdy = dFdy(height);
 
    return PerturbNormal(normal, dpdx, dpdy, dhdx, dhdy);
}

#if 0
float ComputeHeightFogFactor(float vertexHeight, float cameraHeight)
{
    const float fogRangeHigh = 30.0f;
    const float fogRangeLow = 0.0f;
    
    float vertexFogDensity = (fogRangeHigh - vertexHeight) / (fogRangeHigh - fogRangeLow);
    float cameraFogDensity = (fogRangeHigh - cameraHeight) / (fogRangeHigh - fogRangeLow);

    float fogFactor = clamp(vertexFogDensity + cameraFogDensity, 0.0f, 1.0f);
    
    return fogFactor;
}
#endif

float EvalLightGlow(vec3 pos, vec3 lightPos, vec3 cameraPosition, vec3 V, float glowFactor /* = 0.2f */)
{
    vec3 LightToCam = cameraPosition-lightPos;
    float t0 = dot(LightToCam, V);
    
    vec3 temp = -V*t0+cameraPosition-lightPos;
    
    float d0 = length(temp);
    float t = length(cameraPosition-pos);
    
    float intensity = glowFactor * (atan((t-t0)/d0) - atan(-t0/d0)) / d0;
    
    return intensity;
}

// distance-based illumination from center
float ComputeCoreGlowFactor(vec3 pos, float coreGlowRange)
{
    //const float coreGlowRange = 6.5f;

    float dist = abs(length(pos.xz));
    return 1.0f - clamp(dist/coreGlowRange, 0.0f, 1.0f);    
}

#define NUM_LIGHTS  6

PointLight light[NUM_LIGHTS] = {
    // main light
    {
        vec3(0, 0, 0),
        vec3(0.5, 0.25, 0.05),
        1.0 / (90.0*90.0)
    },
    // chamber 1
    {
        //vec3(14.4, 78.6, 43.7),
        vec3(14.5, 78.5, 43.4),
        vec3(0.95, 0.25, 0.05),
        1.0 / (5.0*5.0)
    },
    // chamber 2
    {
        vec3(41.0, 78.5, 20.4),
        vec3(0.95, 0.25, 0.05),
        1.0 / (5.0*5.0)
    },
    // below
    {
        vec3(-5.0, -24.0, 15.0),
        //vec3(0.125, 0.21, 0.05),
        vec3(0.06, 0.1, 0.025),
        1.0 / (40.0*40.0)
    },
    // scarab
    {
        vec3(0.3, 155.0, -3.2),
        vec3(0.012, 0.02, 0.005),
        1.0 / (15.0*15.0)
    },
    {
        vec3(-10.0, 165.0, 23.0),
        vec3(0.005, 0.0025, 0.0005),
        1.0 / (90.0*90.0)
    }
};

void main() {

    float flicker = (0.5f * sin(120.0f*time) + 0.5f);

    //float specularAmount = noise(inPosition);
//    float specularAmount = max(noise(inPosition)-0.5, 0.0) * 2.0;
//    float specularAmount = max(2.0*noise(inPosition*0.25)-1.0, 0.0) * 1.0;
//    float specularAmount = max(2.0*fbm(inPosition*0.25)-1.0, 0.0) * 1.0;
    float specularAmount = max(fbm(inPosition*0.5), 0.0) * 1.0;
    //float specularAmount = 1.0;

#if 0
    // no bump
    vec3 N = normalize(inNormal);
#else
    // bump mapping
    vec3 n = normalize(inNormal); 

    //float height = specularAmount * 0.1;
    float height = max(2.0*noise(inPosition*0.5)-1.0, 0.0) * 0.05;

    vec3 N = CalculateSurfaceNormal(inPosition, n, height);
#endif

    vec3 V = normalize(cameraPosition.xyz-inPosition);
    
    float NdotV = max(dot(N, V), 0.0f);

    light[1].color = vec3(0.95,0.25,0.05) * ((0.5f * sin(5.0f*time) + 0.5f) * 0.9 + 0.1);
    light[2].color = vec3(0.95,0.25,0.05) * ((0.5f * sin(5.0f*time+1.0) + 0.5f) * 0.9 + 0.1);

    light[0].position = vec3(15.0f*sin(time*1.0f),cameraPosition.y+2.0f,15.0f*cos(time*1.0f));
    
    vec3 L = normalize(light[0].position - inPosition); // XXX remove, only needed by projector

//    LightingInfo lightInfo = EvalPointLight(light, inPosition, N, V);

//    float intensity = EvalLightGlow(inPosition, light[0].position, cameraPosition, V, 2.2f);

//    vec3 lightProjector = fbm(L*4.0f) * vec3(0.95f,0.85f,0.8f) *0.5f;
    vec3 lightProjector = pow(fbm(L*4.0f), 2.0f) * vec3(0.95f,0.85f,0.8f) *0.95f;

//    outColor = vec4(lambert*vec3(1,1,1), 1.0f);
    //outColor = vec4(intensity*vec3(1,1,1), 1.0f);
//    outColor = vec4(lambert*intensity*vec3(0.5,0.25,0.05)*1.0f, 1.0f);
    
    //
    // reactor core: distance-based illumination from center
    //

    #if 0
    float dist = abs(length(inPosition.xz));
    const float coreGlowRange = 6.5f;
    float coreGlowFactor = 1.0f - clamp(dist/coreGlowRange, 0.0f, 1.0f);
    #endif
    float coreGlowFactor = ComputeCoreGlowFactor(inPosition, 6.5);

    if((inPosition.y < 7.0) || (inPosition.y > 115.0)) coreGlowFactor = 0.0;

    const vec3 coreGlowColor = vec3(1.0f, 0.5f, 0.0f);

    vec3 coreGlowResult = coreGlowFactor*8.0f*coreGlowColor;

    //outColor = vec4(inColor + coreGlowResult, 1.0f);
    //outColor = vec4(lambert*intensity*vec3(0.5,0.25,0.05)*1.0f + coreGlowResult, 1.0f);

    //
    // reactor core: glow volume
    //

    // NOTE: should probably better do this in view-space

    vec2 res = vec2(-1,-1);

    if ((inPosition.y > 0.0) && ((inPosition.y < 130.0)))
    {
        res = intersectRayCylinder( inPosition, V, vec3(0,0,0), vec3(0,1,0), 4.5f );
    }

    //vec2 res = intersectRayCylinder( inPosition, V, vec3(0,0,0), vec3(0,1,0), 4.5f );
//    vec2 res = intersectRayCylinder( cameraPosition, -V, vec3(0,0,0), vec3(0,1,0), 6.5f );


    vec3 glowColor;

    vec3 cameraToCore = normalize(-cameraPosition+vec3(0, inPosition.y, 0));
    float facingCore = dot(cameraToCore, -V);

    if ((res.x >= 0.0f) && (facingCore > 0.0f)) // && (res.x < 90.0f))
    {
        float d = (res.y-res.x) * 0.1f;
        d *= d;
    //    float d = pow((res.y-res.x)*0.04f, 8.0f);

        glowColor = coreGlowColor * vec3(1.0f, 0.5f, 0.5f) * d; //vec3(d,d,d);
        glowColor *= flicker;
    }
    else
        glowColor = vec3(0,0,0);


    //
    // fresnel / rim light
    //

//    float headLight = pow(NdotV, 8.0f);

    float rimFactor = 1.0f - pow(NdotV, 0.5);
//    vec3 rim = vec3(1,0.25f,0) * max(rimFactor, 0.0f) * 0.2f;
    vec3 rim = vec3(0.1f,0.5f,0.2f) * max(rimFactor, 0.0) * 0.025;

    //outColor = vec4(rim, 1.0f);

    //
    // ray-trace reflections
    //

    vec3 refColor = vec3(0,0,0);

//    if((mod(gl_FragCoord.y, 2.0) < 1.0)) // && (inPosition.y < 130.0))
    if(inPosition.y < 190.0)
    //if((mod(gl_FragCoord.y, 2.0) < 1.0) && (inPosition.y < 190.0))
    {
        res = intersectRayCylinder( inPosition, reflect(-V, N), vec3(0,0,0), vec3(0,1,0), 4.5f );
        if (res.x >= 0.0f)
        {
            float d = (res.y-res.x); // * 0.991f;
            d *= d;

            refColor = coreGlowColor * d * 0.3; // * (1.0f - rimFactor); // * flicker;
//            refColor = coreGlowColor * d * 0.1 * (1.0f - max(rimFactor, 0.0)); // * flicker;
        }
        //else refColor = vec3(0,0,0);
    }

    //
    // heterogeneous fog
    //

    const vec3 fogColor = vec3(1.0f, 1.0f, 0.0f);

#if 0
    float fogFactor = ComputeHeightFogFactor(inPosition.y, cameraPosition.y);
#else
    const float fogDensity = 0.0009; //0.002;
    float distToCamera = length(cameraPosition.xyz-inPosition);
//    float fogFactor = exp(-pow(fogDensity * fbm(cameraPosition.xyz) * distToCamera,2.0));

    float d = distToCamera/4.0;
    vec3 pos = inPosition + vec3(0,-time*10.0,0); //vec3(0,0,0); //cameraPosition.xyz;
    float turb = 0.0;
    for (int i = 0; i < 4; i++) {
        turb += noise(pos*0.15) / pow(2.0,i);
        //turb += ComputeCoreGlowFactor(pos, 30.0) * 3.0;
        //turb *= ComputeCoreGlowFactor(pos, 60.0) * 2.0;
        turb += ComputeCoreGlowFactor(pos, 40.0) * 1.0;

        pos += (V * d);
    }
    //float fogFactor = fogDensity * turb * distToCamera; //
    float fogFactor = 1.0-exp(-pow(fogDensity * turb * distToCamera, 2.0));
#endif

//    vec3 result = mix(inColor*0.2f + rim + fogColor*headLight*0.5f + coreGlowResult, fogColor, fogFactor);
    //outColor = vec4( mix(lambert*intensity*vec3(0.5,0.25,0.05)*1.0f + coreGlowResult, fogColor, fogFactor) , 1.0f);
//last    vec3 result = mix(lambert*intensity*vec3(0.5,0.25,0.05)*1.0f + coreGlowResult, fogColor, fogFactor);

//    vec3 prefog = lambert*intensity*vec3(0.5,0.25,0.05)*1.0f * lightProjector + specular * vec3(1,1,1) * 0.02f * specularAmount * refColor + rim + glowColor + coreGlowResult;
    //vec3 prefog = lambert*intensity*vec3(0.5,0.25,0.05)*1.0f * lightProjector + (specular * vec3(1,1,1) * 0.02f * specularAmount + rim ) * refColor + rim + glowColor + coreGlowResult;

    //
    // point lights
    //

//    vec3 diffuse = lightInfo.diffuse*intensity*1.0f * lightProjector;
//    vec3 specular = lightInfo.specular * 0.02f * specularAmount;

    vec3 diffuse = vec3(0.0);
    vec3 specular = vec3(0.0);

    for (int i=0; i < NUM_LIGHTS; i++)
    {
        LightingInfo lightInfo = EvalPointLight(light[i], inPosition, N, V);

        float intensity = EvalLightGlow(inPosition, light[i].position, cameraPosition, V, 4.0+i*1.0); //2.2f);

        diffuse += lightInfo.diffuse * intensity;
        specular += lightInfo.specular * 0.005f * specularAmount;
        if (i==0) {
            //diffuse *= (intensity*1.0f * lightProjector);
            diffuse *= lightProjector;
            //specular = (specular * 0.02f * specularAmount + rim) * refColor;
//            specular = (specular * 2.0 + rim) * refColor;
            specular = specular + rim + refColor * 0.002f;
        }
    }

//    vec3 prefog = diffuse + (specular + rim) * refColor + rim + glowColor + coreGlowResult;
//    vec3 prefog = diffuse + (specular * 0.02f * specularAmount + rim) * refColor + rim + glowColor + coreGlowResult;
    //vec3 prefog = diffuse + specular + rim + glowColor + coreGlowResult;
    vec3 prefog = diffuse + specular + glowColor + coreGlowResult;

    // apply height fog
    vec3 result = mix(prefog, fogColor, fogFactor);
//    vec3 result = lightProjector;
//vec3 result = vec3(fogFactor);
//vec3 result = vec3(refColor);

#if 1
    // luminance / desaturate / tint
//    float lum = result.r * 0.33 + result.g * 0.33 + result.b * 0.33;
    float lum = result.r * 0.25 + result.g * 0.5 + result.b * 0.25;
    //lum = sqrt(lum+0.001);
    lum = pow(lum, 1.5);
    //result = vec3(lum, lum, lum);
//    result = mix(result, vec3(lum, lum, lum), (0.5f * sin(1.0f*time) + 0.5f));
    result = mix(result, vec3(0.05, 0.5, 0.0)*lum, 0.5);
#endif

    // exposure
    result *= 2.0f;

    // apply gamma correction
    result = pow(result, vec3(1.0/u_gamma));

    // output tonemapped result
    outColor = vec4(tanh(result), 1.0f);
}
