
#version 330

layout(points) in;
layout(triangle_strip, max_vertices = 96) out;

#define MAX_LIGHTS 2
uniform struct Light {
    int flags;
    vec4 position;
    vec4 direction;
    mat4 viewMatrix;
    vec3 colour;
    float ambient;
    sampler2D s_projTexture;
    vec3 projColour;
} u_light[MAX_LIGHTS];

#define LIGHT_FLAG_ENABLED 1
#define LIGHT_FLAG_TEXTURED 2

uniform struct Material {
    int flags;
    vec3 diffuse;
    vec3 specular;
    float specularExp;
} u_material;

#define MATERIAL_FLAG_TEXTURED 2

uniform mat4 u_projectionMatrix;
uniform mat4 u_cameraMatrix;

const vec4 eyePos = vec4(0, 0, 0, 1);

in vec4 v_colour[];
out vec4 f_colour;


void applyLight(Light light, Material surface, vec4 p, vec4 n) {
    vec4 l2p = normalize(p - (u_cameraMatrix * light.position));
    vec4 p2e = normalize(eyePos - p);
    vec3 halfplane = normalize(p2e - l2p).xyz;

    float ndotl = max(0.0, -dot(n, u_cameraMatrix * light.direction));
    float ndoth = dot(n.xyz, halfplane);

    f_colour.rgb += (light.colour * v_colour[0].rgb * light.ambient);
    f_colour.rgb += (ndotl * light.colour * v_colour[0].rgb * surface.diffuse);
    if (ndoth > 0.0) {
        f_colour.rgb += pow(ndoth, surface.specularExp) * surface.specular * light.colour;
    }
}


mat4 rotationMatrix(vec3 axis, float angle)
{
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;
    
    return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                0.0,                                0.0,                                0.0,                                1.0);
}

const float gridGap = 0.04;

vec4[8] nearestGridPoints(vec4 p) {
    const float gap = gridGap;
    float xL = floor(p.x / gap) * gap;
    float xR =  ceil(p.x / gap) * gap;
    float yB = floor(p.y / gap) * gap;
    float yT =  ceil(p.y / gap) * gap;
    float zF = floor(p.z / gap) * gap;
    float zB =  ceil(p.z / gap) * gap;

    vec4 ret[8];
    ret[0] = vec4(xR, yT, zB, 1);
    ret[1] = vec4(xL, yB, zB, 1);
    ret[2] = vec4(xR, yB, zB, 1);
    ret[3] = vec4(xL, yT, zB, 1);
    ret[4] = vec4(xR, yT, zF, 1);
    ret[5] = vec4(xL, yB, zF, 1);
    ret[6] = vec4(xR, yB, zF, 1);
    ret[7] = vec4(xL, yT, zF, 1);

    return ret;
}

void emitFaceVertex(vec4 p, vec4 n) {
    gl_Position = u_projectionMatrix * p;
    f_colour = vec4(0.0, 0.0, 0.0, 1.0);
    applyLight(u_light[0], u_material, p, n);
    EmitVertex();
}


float rand(vec2 co){
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

void main() {
    f_colour = vec4(v_colour[0].rgb, 1);

    const float M_TAU = 6.28318530718;

    float dist, size;

vec4 gp = gl_in[0].gl_Position;
mat4 rot = rotationMatrix(vec3(0,1,0.5), v_colour[0].a);

if (gp.z > gridGap) {
    return;
}

vec4 gpp = abs(u_projectionMatrix * gp);
if (gpp.x/gpp.w > 1 || gpp.y/gpp.w > 1) {
    return;
}



float pd = length(gp);

int numDots = 8;
if (pd > 6.5) {
    numDots = 4;
}
if (pd > 13.0) {
    numDots = 2;
}

    vec4 eyeVec;

const float maxDist = gridGap;

    vec4 gridPositions[8] = nearestGridPoints(gl_in[0].gl_Position);
    vec4 n;
    vec4 verts[8];
    for (int i = 0; i < numDots; i++) {
        dist = distance(gl_in[0].gl_Position, gridPositions[i]);
        size = maxDist - min(maxDist, dist);

        size *= 0.5;
        verts[0] = (gridPositions[i] + rot * vec4(-size, -size,  size, 0));
        verts[1] = (gridPositions[i] + rot * vec4( size, -size,  size, 0));
        verts[2] = (gridPositions[i] + rot * vec4(-size,  size,  size, 0));
        verts[3] = (gridPositions[i] + rot * vec4( size,  size,  size, 0));
        verts[4] = (gridPositions[i] + rot * vec4( size, -size, -size, 0));
        verts[5] = (gridPositions[i] + rot * vec4(-size, -size, -size, 0));
        verts[6] = (gridPositions[i] + rot * vec4( size,  size, -size, 0));
        verts[7] = (gridPositions[i] + rot * vec4(-size,  size, -size, 0));

        n = rot * vec4(0, 0, 1, 0);
        eyeVec = verts[0];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[0], n);
            emitFaceVertex(verts[1], n);
            emitFaceVertex(verts[2], n);
            emitFaceVertex(verts[3], n);
            EndPrimitive();
        }

        n = rot * vec4(0, 0, -1, 0);
        eyeVec = verts[4];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[4], n);
            emitFaceVertex(verts[5], n);
            emitFaceVertex(verts[6], n);
            emitFaceVertex(verts[7], n);
            EndPrimitive();
        }

        n = rot * vec4(-1, 0, 0, 0);
        eyeVec = verts[5];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[5], n);
            emitFaceVertex(verts[0], n);
            emitFaceVertex(verts[7], n);
            emitFaceVertex(verts[2], n);
            EndPrimitive();
        }

        n = rot * vec4(1, 0, 0, 0);
        eyeVec = verts[1];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[1], n);
            emitFaceVertex(verts[4], n);
            emitFaceVertex(verts[3], n);
            emitFaceVertex(verts[6], n);
            EndPrimitive();
        }

        n = rot * vec4(0, 1, 0, 0);
        eyeVec = verts[6];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[6], n);
            emitFaceVertex(verts[7], n);
            emitFaceVertex(verts[3], n);
            emitFaceVertex(verts[2], n);
            EndPrimitive();
        }

        n = rot * vec4(0, -1, 0, 0);
        eyeVec = verts[5];
        if (dot(n, eyeVec) < 0.0) {
            emitFaceVertex(verts[5], n);
            emitFaceVertex(verts[4], n);
            emitFaceVertex(verts[0], n);
            emitFaceVertex(verts[1], n);
            EndPrimitive();
        }

    }

}
