#version 450 core

layout(quads, fractional_even_spacing, cw) in;

layout(binding = 0) uniform sampler2D heightTexture;
layout(location = 0) uniform mat4 M;

uniform float displacementScale = 0.025f;
uniform float tessellationFactor;

layout(std140, binding = 0) uniform SceneUniforms
{
    mat4 PV;	//camera projection * view matrix
    vec4 eye_w;	//world-space eye position
};

in TC_OUT {
    vec3 position;
    vec2 texCoord;
} te_in[];

out TE_OUT {
    vec3 position;
    vec2 texCoord;
    vec3 normal;
} te_out;

float getHeight(vec2 uv) {
    return texture(heightTexture, uv).r * displacementScale;
}

vec3 calculateSmoothNormal(vec3 pos, vec2 coord) {
    float step = 1.0f / (16.0f * tessellationFactor + 1.0f);

    float hLeft = getHeight(coord + vec2(-step, 0.0f));
    float hRight = getHeight(coord + vec2(step, 0.0f));
    float hUp = getHeight(coord + vec2(0.0f, step));
    float hDown = getHeight(coord + vec2(0.0f, -step));
    
    vec3 tangentX = normalize(vec3(step, hRight - hLeft, 0.0));
    vec3 tangentY = normalize(vec3(0.0, hUp - hDown, step));

    return normalize(cross(tangentY, tangentX));
}

void main() {
    // Interpolate texture coordinates
    vec2 texCoord = mix(
        mix(te_in[0].texCoord, te_in[1].texCoord, gl_TessCoord[0]),
        mix(te_in[3].texCoord, te_in[2].texCoord, gl_TessCoord[0]),
        gl_TessCoord[1]
    );

    // Interpolate the original position
    vec3 position = mix(
        mix(te_in[0].position, te_in[1].position, gl_TessCoord[0]),
        mix(te_in[3].position, te_in[2].position, gl_TessCoord[0]),
        gl_TessCoord[1]
    );

    position.z += texture(heightTexture, texCoord).r * displacementScale;

    te_out.position = (M*vec4(position, 1.0f)).xyz; // Position in world-space
    te_out.texCoord = texCoord;
    te_out.normal   = calculateSmoothNormal(position, texCoord);  
    gl_Position = PV*M*vec4(position, 1.0f);        // Position in screen-space
}