#version 450

#include "common.glsl"
#define BLOCK_TINT_DESC_SET GLSL_TERRAIN_TINT_DESCRIPTOR_SET

#include "worldFrameData.glsl"
#include "ambientLight.glsl"
#include "fog.glsl"
#include "svo.glsl"
#include "ray.glsl"
#include "cone.glsl"
#include "light.glsl"
#include "sky.glsl"

#define SKYBOX_FOG_DENSITY 0.7
#define SOLAR_RADIANCE 6.0
#define SKY_RADIANCE 7.0

layout(
    set = GLSL_TERRAIN_SKYBOX_DESCRIPTOR_SET,
    binding = GLSL_SKYBOX_SUNMAP_INDEX
) uniform samplerCube uSunmapSampler;

layout(location = 0) in vec4 passRayClipVec;

layout(location = 0) out vec4 outColor;
layout(location = 1) out vec4 outNormal;
layout(location = 2) out vec4 outPosSpecular;
layout(location = 3) out vec4 outLight;
layout(location = 4) out vec4 outGodRays;

float getAmbientLightFrom(Ray origRay, vec3 dir)
{
    dir = normalize(dir);

    origRay.dir = dir;
    rayAdvance(origRay);

    Ray sampleRay = rayNew(origRay.coords, origRay.offset, dir);
    rayMarch(sampleRay, 4.0);

    return min(sampleRay.distance / 4.0, 1.0);
}

void main()
{
    vec4 tmp = uFrameData.antiVP * passRayClipVec;
    vec3 rayDir = normalize(tmp.xyz/tmp.w);
    uvec3 rayPos = uFrameData.cameraVoxel;
    vec3 rayOffset = uFrameData.cameraOffset;

    Ray ray = rayNew(rayPos, rayOffset, rayDir);
    rayMarch(ray, 8.0 * uFrameData.farZ);

    if (!ray.hit)
    {
        float fogFactor = getFogFactor(SKYBOX_FOG_DENSITY, 1.0-ray.dir.z);

        vec3 color = skySample(ray.dir, 0.0);
        color = mix(color, uSkyboxData.color, fogFactor);
        color = applyTint(color, ray.tint);

        outColor = vec4(color * rayGetFilterColor(ray), 1.0);
        outNormal = vec4(0.0);
        outPosSpecular = vec4(0.0);
        outLight = vec4(SKY_RADIANCE) * vec4(rayGetFilterColor(ray), 1.0);
        outGodRays = vec4(rayGetFilterColor(ray) * skySampleSun(ray.dir, 0.0), 1.0);
        gl_FragDepth = 1.0f;
    }
    else
    {
        ray.color.rgb = applyTint(ray.color.rgb, ray.tint);
        vec4 worldPos = vec4((vec3(ivec3(ray.coords - uFrameData.cameraVoxel)) + (ray.offset - uFrameData.cameraOffset)) / 8.0, 1.0);

        float ambientLevel = 0.0;

        if ((uFrameData.flags & GLSL_FRAMEDATA_AMBIENT_OCCLUSION) != 0)
        {
            ambientLevel += getAmbientLightFrom(ray, ray.normal + ray.normal.zxy);
            ambientLevel += getAmbientLightFrom(ray, ray.normal - ray.normal.zxy);
            ambientLevel += getAmbientLightFrom(ray, ray.normal + ray.normal.yzx);
            ambientLevel += getAmbientLightFrom(ray, ray.normal - ray.normal.yzx);

            ambientLevel += getAmbientLightFrom(ray, ray.normal + ray.normal.zxy + ray.normal.yzx);
            ambientLevel += getAmbientLightFrom(ray, ray.normal - ray.normal.zxy - ray.normal.yzx);
            ambientLevel += getAmbientLightFrom(ray, ray.normal + ray.normal.yzx + ray.normal.zxy);
            ambientLevel += getAmbientLightFrom(ray, ray.normal - ray.normal.yzx - ray.normal.zxy);

            ambientLevel /= 8.0;
        }
        else
        {
            ambientLevel = 1.0;
        }

        vec3 sunlightIntensity = vec3(SOLAR_RADIANCE);

        if ((uFrameData.flags & GLSL_FRAMEDATA_SHADOWS) != 0)
        {
            vec3 lightDir = (uSkyboxData.model * vec4(0.0, 1.0, 0.0, 0.0)).xyz;

            Ray lightRay = rayNew(ray, lightDir);
            lightRay.allowRefract = false;
            rayMarch(lightRay, 8.0 * uFrameData.farZ);

            if (!lightRay.hit)
            {
                sunlightIntensity = rayGetFilterColor(lightRay) * SOLAR_RADIANCE;
            }
            else
            {
                float intensityByDistance = clamp((sqrt(lightRay.distance / (8.0 * 64.0))), 0.0, 0.3);
                sunlightIntensity = vec3(intensityByDistance) * rayGetFilterColor(lightRay);
            }
        }

        vec3 specularRadiance = vec3(0.0);
        float specular = ray.light.w;
        if (specular != 0.0 && (uFrameData.flags & GLSL_FRAMEDATA_DEBUG_SPECULAR) != 0)
        {
            specular = uFrameData.debugSpecular;
        }

        vec3 ambientLight = computeAmbientLight(worldPos.xyz, ray.normal, specular, ambientLevel, sunlightIntensity);

        if (specular > 0.0 && (uFrameData.flags & GLSL_FRAMEDATA_CONEMARCHING) != 0)
        {
            float roughness = ray.refractiveIndex;

            vec3 noise = calculateNoise((ivec3(ray.coords) << GLSL_SVO_MAX_ORDER) + ivec3(ray.offset * 4.0));
            vec3 tangentNoise = noise - ray.normal * dot(noise, ray.normal);
            float jitterStrength = roughness * 0.018;
            vec3 perturbedNormal = normalize(ray.normal + tangentNoise * jitterStrength);
    
            float coneAngle = coneAngleFromSpecular(specular);
            vec3 specularDir = normalize(reflect(ray.dir, perturbedNormal));

            float NoV = clamp(dot(perturbedNormal, -ray.dir), 0.0, 1.0);
            float F0 = 0.04;
            float fresnel = F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0);

            Cone specularCone = coneNew(ray.coords, ray.offset, specularDir, coneAngle);
            coneMarch(specularCone, 8.0 * uFrameData.farZ);
            specularCone.radiance += skySample(specularCone.dir, 1.0 - specular) * coneGetThroughput(specularCone);

            specularRadiance = specularCone.radiance * fresnel * GLSL_RADIANCE_SCALE;
        }

        outColor = ray.color;
        outNormal = vec4(ray.normal, 1.0);
        outPosSpecular = vec4(worldPos.xyz, specular);
        outLight = vec4(ray.light.rgb * GLSL_RADIANCE_SCALE + ambientLight + specularRadiance, 1.0);
        outGodRays = vec4(0.0, 0.0, 0.0, 1.0);

        vec4 clipPos = uFrameData.proj * uFrameData.view * worldPos;
        gl_FragDepth = clipPos.z / clipPos.w;
    }
}