// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#version 440

#extension GL_GOOGLE_include_directive : enable

#include "../effectlib/oitcommon.glsllib"

layout(std140, binding = 0) uniform buf {
    mat4 qt_modelMatrix;
#if QSHADER_VIEW_COUNT >= 2
    mat4 qt_viewMatrix[QSHADER_VIEW_COUNT];
    mat4 qt_projectionMatrix[QSHADER_VIEW_COUNT];
#else
    mat4 qt_viewMatrix;
    mat4 qt_projectionMatrix;
#endif
#ifdef QSSG_PARTICLES_ENABLE_VERTEX_LIGHTING
    // ParticleLightData struct
    vec4 qt_pointLightPosition[4];
    vec4 qt_pointLightConstantAtt;
    vec4 qt_pointLightLinearAtt;
    vec4 qt_pointLightQuadAtt;
    vec4 qt_pointLightColor[4];
    vec4 qt_spotLightPosition[4];
    vec4 qt_spotLightConstantAtt;
    vec4 qt_spotLightLinearAtt;
    vec4 qt_spotLightQuadAtt;
    vec4 qt_spotLightColor[4];
    vec4 qt_spotLightDirection[4];
    vec4 qt_spotLightConeAngle;
    vec4 qt_spotLightInnerConeAngle;
#endif
    vec4 qt_spriteConfig;
    vec3 qt_light_ambient_total;
    vec2 qt_oneOverParticleImageSize;
    uint qt_countPerSlice;
    float qt_billboard;
    float qt_opacity;
#ifdef QSSG_PARTICLES_ENABLE_VERTEX_LIGHTING
    bool qt_pointLights;
    bool qt_spotLights;
#endif
#if QSSG_OIT_METHOD == QSSG_OIT_WEIGHTED_BLENDED
    vec2 qt_cameraProperties;
#endif
} ubuf;

#ifdef QSSG_PARTICLES_ENABLE_VERTEX_LIGHTING
/* Simplified lighting for particles
 *
 * Treat particles as points -> do not take normal into account
 * -> directional lights become ambient
 * -> point lights only account for attenuation
 * -> spotlights only account for attenuation and spot angles
 */
vec4 qt_attenuate4(in vec4 dist, in vec4 catt, in vec4 latt, in vec4 qatt)
{
    vec4 factors = catt + dist * latt + dist * dist * qatt;
    return vec4(1.0) / factors;
}
vec3 qt_calcPointLights(in vec3 position)
{
    vec4 lengths = vec4(length(position - ubuf.qt_pointLightPosition[0].xyz),
                        length(position - ubuf.qt_pointLightPosition[1].xyz),
                        length(position - ubuf.qt_pointLightPosition[2].xyz),
                        length(position - ubuf.qt_pointLightPosition[3].xyz));
    vec4 attenuations = qt_attenuate4(lengths, ubuf.qt_pointLightConstantAtt, ubuf.qt_pointLightLinearAtt, ubuf.qt_pointLightQuadAtt);

    return ubuf.qt_pointLightColor[0].rgb * attenuations.x + ubuf.qt_pointLightColor[1].rgb * attenuations.y + ubuf.qt_pointLightColor[2].rgb * attenuations.z + ubuf.qt_pointLightColor[3].rgb * attenuations.w;
}
vec3 qt_calcSpotLights(in vec3 position)
{
    vec3 lightVector0 = position - ubuf.qt_spotLightPosition[0].xyz;
    vec3 lightVector1 = position - ubuf.qt_spotLightPosition[1].xyz;
    vec3 lightVector2 = position - ubuf.qt_spotLightPosition[2].xyz;
    vec3 lightVector3 = position - ubuf.qt_spotLightPosition[3].xyz;
    vec4 lengths = vec4(length(lightVector0), length(lightVector1), length(lightVector2), length(lightVector3));
    vec4 attenuations = qt_attenuate4(lengths, ubuf.qt_spotLightConstantAtt, ubuf.qt_spotLightLinearAtt, ubuf.qt_spotLightQuadAtt);
    vec4 spots = vec4(acos(dot(normalize(lightVector0), normalize(ubuf.qt_spotLightDirection[0].xyz))),
                      acos(dot(normalize(lightVector1), normalize(ubuf.qt_spotLightDirection[1].xyz))),
                      acos(dot(normalize(lightVector2), normalize(ubuf.qt_spotLightDirection[2].xyz))),
                      acos(dot(normalize(lightVector3), normalize(ubuf.qt_spotLightDirection[3].xyz))));
    vec4 cones = vec4(1.0 - smoothstep(ubuf.qt_spotLightInnerConeAngle, ubuf.qt_spotLightConeAngle, spots));
    attenuations *= cones;
    return ubuf.qt_spotLightColor[0].rgb * attenuations.x + ubuf.qt_spotLightColor[1].rgb * attenuations.y + ubuf.qt_spotLightColor[2].rgb * attenuations.z + ubuf.qt_spotLightColor[3].rgb * attenuations.w;
}
vec3 qt_calcLightColor(in vec3 position)
{
    vec3 color = vec3(0.0);

    if (ubuf.qt_pointLights)
        color += qt_calcPointLights(position);
    if (ubuf.qt_spotLights)
        color += qt_calcSpotLights(position);
    return color + ubuf.qt_light_ambient_total;
}
#endif // QSSG_PARTICLES_ENABLE_VERTEX_LIGHTING

layout(binding = 2) uniform sampler2D qt_particleTexture;

out gl_PerVertex {
    vec4 gl_Position;
};

#ifdef QSSG_PARTICLES_ENABLE_ANIMATED
const uint particleSize = 4;
#else
const uint particleSize = 3;
#endif

struct Particle
{
    vec3 position;
    float size;
    vec3 rotation;
    float age;
    vec4 color;
#ifdef QSSG_PARTICLES_ENABLE_ANIMATED
    float animationFrame;
    vec3 unusedPadding;
#endif
};

vec2 qt_indexToUV(in uint index)
{
    uint v = index / ubuf.qt_countPerSlice;
    uint u = index - ubuf.qt_countPerSlice * v;
    return (vec2(u * particleSize, v) + vec2(0.5)) * ubuf.qt_oneOverParticleImageSize;
}

Particle qt_loadParticle(in uint index)
{
    Particle p;
    vec2 offset = qt_indexToUV(index);
    vec4 p0 = texture(qt_particleTexture, offset);
    vec4 p1 = texture(qt_particleTexture, vec2(offset.x + ubuf.qt_oneOverParticleImageSize.x, offset.y));
    vec4 p2 = texture(qt_particleTexture, vec2(offset.x + 2.0 * ubuf.qt_oneOverParticleImageSize.x, offset.y));
#ifdef QSSG_PARTICLES_ENABLE_ANIMATED
    vec4 p3 = texture(qt_particleTexture, vec2(offset.x + 3.0 * ubuf.qt_oneOverParticleImageSize.x, offset.y));
#endif
    p.position = p0.xyz;
    p.size = p0.w;
    p.rotation = p1.xyz;
    p.age = p1.w;
    p.color = p2;
#ifdef QSSG_PARTICLES_ENABLE_ANIMATED
    p.animationFrame = p3.x;
#endif
    return p;
}

mat3 qt_fromEulerRotation(in vec3 rotation)
{
    float a = cos(rotation.x);
    float b = sin(rotation.x);
    float c = cos(rotation.y);
    float d = sin(rotation.y);
    float e = cos(rotation.z);
    float f = sin(rotation.z);
    mat3 ret;
    float bd = b*d;
    float ad = a*d;
    ret[0][0] = c * e;
    ret[0][1] = -c * f;
    ret[0][2] = d;
    ret[1][0] = bd*e + a*f;
    ret[1][1] = a*e - bd*f;
    ret[1][2] = -b*c;
    ret[2][0] = b*f - ad*e;
    ret[2][1] = ad*f + b*e;
    ret[2][2] = a*c;
    return ret;
}

float qt_ageToSpriteFactor(in float age)
{
    // Age should be [0, 1], but just in case take the fraction
    return fract(age);
}

layout(location = 0) out vec4 color;
layout(location = 1) out vec2 spriteData;
layout(location = 2) out vec2 texcoord;
layout(location = 3) flat out uint instanceIndex;

const vec2 corners[4] = {{0.0, 0.0}, {1.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}};

void main()
{
    uint particleIndex = gl_InstanceIndex;
    instanceIndex = particleIndex;
    uint cornerIndex = gl_VertexIndex;
    vec2 corner = corners[cornerIndex];
    Particle p = qt_loadParticle(particleIndex);
    mat3 rotMat = qt_fromEulerRotation(p.rotation);
    vec4 worldPos = ubuf.qt_modelMatrix * vec4(p.position, 1.0);
    vec4 viewPos = worldPos;
    vec3 offset = (p.size * vec3(corner - vec2(0.5), 0.0));
    viewPos.xyz += offset * rotMat * (1.0 - ubuf.qt_billboard);
#if QSHADER_VIEW_COUNT >= 2
    viewPos = ubuf.qt_viewMatrix[gl_ViewIndex] * viewPos;
#else
    viewPos = ubuf.qt_viewMatrix * viewPos;
#endif
    viewPos.xyz += rotMat * offset * ubuf.qt_billboard;
    texcoord = corner;
    color = p.color;
    color.a *= ubuf.qt_opacity;

#ifdef QSSG_PARTICLES_ENABLE_VERTEX_LIGHTING
    color.rgb *= qt_calcLightColor(worldPos.xyz);
#endif

#if QSHADER_VIEW_COUNT >= 2
    gl_Position = ubuf.qt_projectionMatrix[gl_ViewIndex] * viewPos;
#else
    gl_Position = ubuf.qt_projectionMatrix * viewPos;
#endif

#ifdef QSSG_PARTICLES_ENABLE_MAPPED
    spriteData.x = qt_ageToSpriteFactor(p.age);
#endif
#ifdef QSSG_PARTICLES_ENABLE_ANIMATED
    spriteData.y = p.animationFrame;
#endif
}

