#version 450
#include "common.glsl"
#include "worldFrameData.glsl"
#include "modelBuffer.glsl"
#include "modelData.glsl"
#include "ambientLight.glsl"

layout(location = 0) in vec3 passVoxelPos;
layout(location = 1) in vec4 passClipPos;
layout(location = 2) in vec3 passModelNormal;

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

bool isWithinRange(vec3 voxelPos)
{
    if (voxelPos.x < 0.0 || voxelPos.y < 0.0 || voxelPos.z < 0.0)
    {
        return false;
    }

    uvec3 coords = uvec3(voxelPos);
    return coords.x < modelBuffer.size.x && coords.y < modelBuffer.size.y && coords.z < modelBuffer.size.z;
}

uint getVoxelAt(vec3 voxelPos)
{
    uvec3 coords = min(uvec3(max(voxelPos, vec3(0.0))), modelBuffer.size.xyz - 1);
    uint index = coords.z * modelBuffer.size.y * modelBuffer.size.x + coords.y * modelBuffer.size.x + coords.x;
    return modelBuffer.voxels[index];
}

void main()
{
    vec3 clipRayDir = passClipPos.xyz / passClipPos.w;
    clipRayDir.z = 1.0;
    vec3 rayDir = normalize((uModelData.antiMVP * vec4(clipRayDir, 1.0)).xyz);
    vec3 rayPos = passVoxelPos + 0.0001 * rayDir;

    vec3 normal = passModelNormal;

    uint color;
    while (((color = getVoxelAt(rayPos)) & 0xFF) == 0)
    {
        vec3 stepSize = vec3(
            rayDir.x > 0 ? 1.0 - fract(rayPos.x) : fract(rayPos.x),
            rayDir.y > 0 ? 1.0 - fract(rayPos.y) : fract(rayPos.y),
            rayDir.z > 0 ? 1.0 - fract(rayPos.z) : fract(rayPos.z)
        );

        stepSize /= abs(rayDir);
        float minDistanceToNextVoxel = min(stepSize.x, min(stepSize.y, stepSize.z));

        float isX = float(minDistanceToNextVoxel == stepSize.x);
        float isY = float(minDistanceToNextVoxel == stepSize.y) * (1.0 - isX);
        float isZ = (1.0 - isX) * (1.0 - isY);
        normal = -normalize(rayDir * vec3(isX, isY, isZ));

        rayPos += (minDistanceToNextVoxel + 0.0001) * rayDir;

        if (!isWithinRange(rayPos))
        {
            break;
        }
    }

    if ((color & 0xFF) == 0)
    {
        discard;
    }

    vec4 worldPos = uModelData.modelMatrix * vec4(rayPos / 8.0, 1.0);
    vec4 clipPos = uFrameData.proj * uFrameData.view * worldPos;
    gl_FragDepth = clipPos.z / clipPos.w;

    vec3 ambientLight = computeAmbientLight(worldPos.xyz, normal, 0.0, 1.0, vec3(1.0));

    outColor = decodeSRGBA8(color);
    outNormal = vec4(normalize((uModelData.modelMatrix * vec4(normal, 0.0)).xyz), 1.0);
    outPosSpecular = vec4(worldPos.xyz, 0.0);
    outLight = vec4(ambientLight, 1.0);
}