#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
    uint mode;
    uint angleType;
} ubo;

layout(push_constant) uniform PushConsts {
    uint wallDrawMode;
} pushConstants;

layout(set = 1, binding = 2) uniform UniformBufferObjectDynamic {
    mat4 model;
    layout(row_major) mat4 bones[254];
    vec3 selectionColour;
    uint isSelected;
    float rotation;
    uint extraFlags;
} ubod;

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec3 inColour;
layout(location = 3) in vec2 inTexCoord;
layout(location = 8) in uvec4 inBlendIndices;
layout(location = 9) in uvec4 inBlendWeights;

layout(location = 0) out vec3 fragColour;
layout(location = 1) out vec2 fragTexCoord;
layout(location = 2) out vec3 fragSelectionColour;
layout(location = 3) out uint fragIsSelected;
layout(location = 4) out vec3 fragNormal;
layout(location = 5) out uint extraFlags;
layout(location = 6) out vec3 fragCoord;

out gl_PerVertex {
    vec4 gl_Position;
};

/**
 * Rotate the given vector v around the Y axis (origin)
 */
vec3 rotate(vec3 v, float angle) {
    float s = sin(angle);
    float c = cos(angle);
    return vec3(v.x * c + v.z * s, v.y, -1 * v.x * s + v.z * c);
}

void main() {
    bool hideCompletely = false;
    // before doing anything, check if we are supposed to show this, if this is a portal model
    if (pushConstants.wallDrawMode != 1) {
        // if the first two bits are 1 or 2, this is either a door or window.
        // also, the draw mode is completely lowered, so do not draw anything then.
        bool isPortal = ((ubod.extraFlags & 1U) == 1 || (ubod.extraFlags & 2U) == 2); // this is a door or window
        bool isLowered = (pushConstants.wallDrawMode == 3); // the "lowered" mode
        bool isMixed = (pushConstants.wallDrawMode == 2); // the "standard" mode, lower if a tile is blocked by it
        bool isBlocking = (((ubod.extraFlags >> 2) >> (ubo.angleType)) & 1U) == 0; // check if this is blocking something in the current view direction
        if (isPortal && (isLowered || (isMixed && isBlocking))) {
            hideCompletely = true;
        }
    }

    // if the 8th bit is set to 1 to indicate we may try to hide certain (door) vertices
    // and the vertex is marked to be hidden (by having a maximum red value), hide the vertex
    if ((((ubod.extraFlags >> 7) & 1U) == 1) && inColour.r == 1) {
        hideCompletely = true;
    }

    if (hideCompletely) {
        gl_Position = vec4(0, 0, 0, 0);
        fragColour = vec3(0, 0, 0);
        fragTexCoord = vec2(0, 0);
        fragSelectionColour = vec3(0, 0, 0);
        fragIsSelected = 0;
        extraFlags = 0;
        fragNormal = vec3(0, 0, 0);
        return;
    }

    vec4 normalisedWeights;
    normalisedWeights[0] = float(inBlendWeights[0]) / 255;
    normalisedWeights[1] = float(inBlendWeights[1]) / 255;
    normalisedWeights[2] = float(inBlendWeights[2]) / 255;
    normalisedWeights[3] = float(inBlendWeights[3]) / 255;
    mat4 boneTransform = mat4(1.0);
    // since we only support up to 254 bones (see MAX_BONES),
    // anything above this value means this is not anymated
    // and we use the identify matrix isntead
    if (inBlendIndices[0] <= 254) {
        boneTransform = (ubod.bones[inBlendIndices[0]] * normalisedWeights[0]);
        boneTransform += (ubod.bones[inBlendIndices[1]] * normalisedWeights[1]);
        boneTransform += (ubod.bones[inBlendIndices[2]] * normalisedWeights[2]);
        boneTransform += (ubod.bones[inBlendIndices[3]] * normalisedWeights[3]);
    }

    mat4 modelView = ubo.view * ubod.model;
    gl_Position = ubo.proj * modelView * boneTransform * vec4(inPosition, 1.0);
    fragColour = inColour;
    fragTexCoord = inTexCoord;
    fragSelectionColour = ubod.selectionColour;
    fragIsSelected = ubod.isSelected;
    fragCoord = (ubod.model * boneTransform * vec4(inPosition, 1.0)).xyz;
    extraFlags = ubod.extraFlags;
    // lighting information passed on to the fragment shader
    fragNormal = rotate(inNormal, ubod.rotation);
  }