///////////////////////////////////////////

uniform vec4 draw_texture_coords;
uniform vec2 draw_texture_dims;
uniform mediump vec4 draw_texture_normalized_crop_rect[kMaxTexUnits];

// Varyings/////////////////////////////////////////////////////////////////////

out vec4 pos_varying;
out vec3 normal_varying;
out mediump vec4 color_varying;
flat out mediump vec4 color_varying_flat;
#if kTexUnits >= 1u
out mediump vec3 texcoord0_varying;
#endif
#if kTexUnits >= 2u
out mediump vec3 texcoord1_varying;
#endif
#if kTexUnits >= 3u
out mediump vec3 texcoord2_varying;
#endif
#if kTexUnits >= 4u
out mediump vec3 texcoord3_varying;
#endif

float posDot(vec3 a, vec3 b)
{
    return max(dot(a, b), 0.0);
}

mediump vec4 doLighting(mediump vec4 vertexColor)
{
    mediump vec4 materialAmbientActual = material_ambient;
    mediump vec4 materialDiffuseActual = material_diffuse;

    if (enable_color_material)
    {
        materialAmbientActual = vertexColor;
        materialDiffuseActual = vertexColor;
    }

    mediump vec4 lightingResult = material_emissive + materialAmbientActual * light_model_scene_ambient;

    for (uint i = 0u; i < kMaxLights; i++)
    {

        if (!light_enables[i])
            continue;

        mediump vec4 lightAmbient  = light_ambients[i];
        mediump vec4 lightDiffuse  = light_diffuses[i];
        mediump vec4 lightSpecular = light_speculars[i];
        vec4 lightPos      = light_positions[i];
        vec3 lightDir      = light_directions[i];
        float attConst     = light_attenuation_consts[i];
        float attLinear    = light_attenuation_linears[i];
        float attQuadratic = light_attenuation_quadratics[i];
        float spotAngle    = light_spotlight_cutoff_angles[i];
        float spotExponent = light_spotlight_exponents[i];

        vec3 toLight;
        if (lightPos.w == 0.0)
        {
            toLight = lightPos.xyz;
        }
        else
        {
            toLight = (lightPos.xyz / lightPos.w - pos_varying.xyz);
        }

        float lightDist = length(toLight);
        vec3 toLightNormalized = normalize(toLight);
        vec3 h                 = toLightNormalized + vec3(0.0, 0.0, 1.0);
        float ndotL            = posDot(normal_varying, toLightNormalized);
        float ndoth            = posDot(normal_varying, normalize(h));

        float specAtt;

        if (ndotL != 0.0)
        {
            specAtt = 1.0;
        }
        else
        {
            specAtt = 0.0;
        }

        float att;

        if (lightPos.w != 0.0)
        {
            float attDenom =
                (attConst + attLinear * lightDist + attQuadratic * lightDist * lightDist);
            att = 1.0 / attDenom;
        }
        else
        {
            att = 1.0;
        }

        mediump float spot;

        mediump float spotAngleCos = cos(radians(spotAngle));
        vec3 toSurfaceDir  = -toLightNormalized;
        mediump float spotDot      = posDot(toSurfaceDir, normalize(lightDir));

        if (spotAngle == 180.0 || lightPos.w == 0.0)
        {
            spot = 1.0;
        }
        else
        {
            if (spotDot < spotAngleCos)
            {
                spot = 0.0;
            }
            else
            {
                spot = pow(spotDot, spotExponent);
            }
        }

        mediump vec4 contrib = materialAmbientActual * lightAmbient;
        contrib += ndotL * materialDiffuseActual * lightDiffuse;
        if (ndoth > 0.0 && material_specular_exponent > 0.0)
        {
            contrib += specAtt * pow(ndoth, material_specular_exponent) * material_specular *
                       lightSpecular;
        }
        else
        {
            if (ndoth > 0.0)
            {
                contrib += specAtt * material_specular * lightSpecular;
            }
        }
        contrib *= att * spot;
        lightingResult += contrib;
    }

    return lightingResult;
}

const mediump vec4 drawTextureVertices[6] = vec4[](
    vec4(0.0, 0.0, 0.0, 1.0),
    vec4(1.0, 0.0, 0.0, 1.0),
    vec4(1.0, 1.0, 0.0, 1.0),
    vec4(0.0, 0.0, 0.0, 1.0),
    vec4(1.0, 1.0, 0.0, 1.0),
    vec4(0.0, 1.0, 0.0, 1.0));

mediump vec4 drawTexturePosition(int vertexId)
{
    // The texture is drawn in the XY plane, so Z is constant.
    vec2 positionXY = draw_texture_coords.xy + drawTextureVertices[vertexId].xy * draw_texture_dims;
    return vec4(positionXY, draw_texture_coords.z, 1.0);
}

mediump vec3 drawTextureTexCoord(int vertexId, uint textureUnit)
{
    // The texture is drawn in the XY plane, so Z is 0.
    mediump vec2 texCropPos = draw_texture_normalized_crop_rect[textureUnit].xy;
    mediump vec2 texCropDim = draw_texture_normalized_crop_rect[textureUnit].zw;
    mediump vec2 texCoords  = texCropPos + drawTextureVertices[vertexId].xy * texCropDim;

    return vec3(texCoords, 0.0);
}

vec4 calcWorldPosition(vec4 posInput)
{
    return modelview * posInput;
}

vec4 calcNdcFromWorldPosition(vec4 worldPos)
{
    return projection * worldPos;
}

float calcPointSize(vec4 ndcPos)
{
    float dist         = length(ndcPos.z);
    float attConst     = point_distance_attenuation[0];
    float attLinear    = point_distance_attenuation[1];
    float attQuad      = point_distance_attenuation[2];
    float attPart      = attConst + attLinear * dist + attQuad * dist * dist;
    float attPointSize = pointsize / pow(attPart, 0.5);

    return clamp(attPointSize, point_size_min, point_size_max);
}

vec3 calcNormal(vec3 normalInput)
{
    mat3 mvInvTr3 = mat3(modelview_invtr);
    vec3 result   = mvInvTr3 * normalInput;

    if (enable_rescale_normal)
    {
        float rescale   = 1.0;
        vec3 rescaleVec = vec3(mvInvTr3[2]);
        float len       = length(rescaleVec);
        if (len > 0.0)
        {
            rescale = 1.0 / len;
        }
        result *= rescale;
    }

    if (enable_normalize)
    {
        result = normalize(result);
    }

    return result;
}

void main()
{
    if (enable_draw_texture)
    {
        int vertexId        = gl_VertexID;
        mediump vec4 posDrawTexture = drawTexturePosition(vertexId);

        gl_Position = posDrawTexture;
        pos_varying = posDrawTexture;

        normal_varying = normal;

        gl_PointSize = pointsize;

#if kTexUnits >= 1u
        texcoord0_varying = drawTextureTexCoord(vertexId, 0u);
#endif
#if kTexUnits >= 2u
        texcoord1_varying = drawTextureTexCoord(vertexId, 1u);
#endif
#if kTexUnits >= 3u
        texcoord2_varying = drawTextureTexCoord(vertexId, 2u);
#endif
#if kTexUnits >= 4u
        texcoord3_varying = drawTextureTexCoord(vertexId, 3u);
#endif
    }
    else
    {
        vec4 worldPos = calcWorldPosition(pos);
        vec4 ndcPos   = calcNdcFromWorldPosition(worldPos);

        gl_Position = ndcPos;
        pos_varying = worldPos;

        normal_varying = calcNormal(normal);

        // Avoid calculating point size stuff
        // if we are not rendering points.
        if (point_rasterization)
        {
            gl_PointSize = calcPointSize(ndcPos);
        }
        else
        {
            gl_PointSize = pointsize;
        }

#if kTexUnits >= 1u
        texcoord0_varying = (texture_matrix[0] * texcoord0).xyz;
#endif
#if kTexUnits >= 2u
        texcoord1_varying = (texture_matrix[1] * texcoord1).xyz;
#endif
#if kTexUnits >= 3u
        texcoord2_varying = (texture_matrix[2] * texcoord2).xyz;
#endif
#if kTexUnits >= 4u
        texcoord3_varying = (texture_matrix[3] * texcoord3).xyz;
#endif
    }

    mediump vec4 vertex_color = color;

    if (enable_lighting)
    {
        vertex_color = doLighting(color);
    }

    vertex_color = clamp(vertex_color, vec4(0), vec4(1));

    color_varying      = vertex_color;
    color_varying_flat = vertex_color;
}
