#version 450

layout(quads, equal_spacing, ccw) in;

layout(push_constant) uniform constants {
    mat4 lightSpaceMatrix;
};

layout(location = 0) in vec3 iPos[];
layout(location = 1) flat in int iChunkID[];

layout(set = 0, binding = 0) uniform uGlobal {
    mat4 view;
    mat4 proj;
    vec4 camPos;
};

layout(set = 0, binding = 1) uniform uEntity {
    mat4 model;
    vec4 noiseSize;
    vec4 color;
};

layout(set = 0, binding = 2) readonly buffer uHeights {
    float heights[];
};

layout(location = 0) out float oHeight;
layout(location = 1) out vec3 oNormal;
layout(location = 2) out vec3 oWorldPos;
layout(location = 3) out vec3 oColor;
layout(location = 4) out vec4 oLightFragPos;

float height(vec2 id) {
    ivec2 noiseRes = ivec2(noiseSize.xy);
    vec2 gridPos = id * float(noiseRes - 1);

    ivec2 base = ivec2(floor(gridPos));
    vec2 frac = fract(gridPos);

    int idx00 = base.y * noiseRes.y + base.x;
    int idx10 = base.y * noiseRes.y + min(base.x + 1, noiseRes.x - 1);
    int idx01 = min(base.y + 1, noiseRes.y - 1) * noiseRes.x + base.x;
    int idx11 = min(base.y + 1, noiseRes.y - 1) * noiseRes.x + min(base.x + 1, noiseRes.x - 1);

    int offset = iChunkID[0] * noiseRes.x * noiseRes.y;

    float h00 = heights[offset + idx00];
    float h10 = heights[offset + idx10];
    float h01 = heights[offset + idx01];
    float h11 = heights[offset + idx11];

    return mix(mix(h00, h10, frac.x), mix(h01, h11, frac.x), frac.y);
}

void main() {
    vec2 id = gl_TessCoord.xy;
    vec4 p0 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, id.x);
    vec4 p1 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, id.x);
    vec4 pos = mix(p0, p1, id.y);

    float scale = 1.0;
    float h = height(id); // float(iChunkID[0]) * 0.5;

    // normal calculation
    float ep = 1.0 / (noiseSize.xy - 1.0).x;
    float right = height(vec2(clamp(id.x + ep, 0, 1), id.y));
    float left = height(vec2(clamp(id.x - ep, 0, 1), id.y));
    float up = height(vec2(id.x, clamp(id.y + ep, 0, 1)));
    float down = height(vec2(id.x, clamp(id.y - ep, 0, 1)));

    float dx = min(id.x + ep, 1.0) - max(id.x - ep, 0.0);
    float dz = min(id.y + ep, 1.0) - max(id.y - ep, 0.0);
    float slopeX = (right - left) / dx;
    float slopeZ = (up - down) / dz;
    vec3 normal = normalize(cross(vec3(0, slopeZ, 1), vec3(1, slopeX, 0)));

    pos.y += h * scale;
    gl_Position = proj * view * model * pos;

    oHeight = h;
    oNormal = normal;
    oWorldPos = (model * pos).xyz;
    oColor = color.rgb;
    oLightFragPos = lightSpaceMatrix * vec4(oWorldPos, 1.0f);
}
