#version 450
#pragma shader_stage(compute)
layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

layout(binding = 0) uniform samplerCube environmentMap;
layout(binding = 1, rgba16f) uniform writeonly image2DArray irradianceMap;

const float PI = 3.14159265359;

vec3 getDirection(uint face, vec2 uv) {
	vec3 dir;
	uv = uv * 2.0 - 1.0;
	switch(face) {
		case 0: dir = vec3( 1.0, -uv.y, -uv.x); break; // POSITIVE_X
		case 1: dir = vec3(-1.0, -uv.y,  uv.x); break; // NEGATIVE_X
		case 2: dir = vec3( uv.x,  1.0,  uv.y); break; // POSITIVE_Y
		case 3: dir = vec3( uv.x, -1.0, -uv.y); break; // NEGATIVE_Y
		case 4: dir = vec3( uv.x, -uv.y,  1.0); break; // POSITIVE_Z
		case 5: dir = vec3(-uv.x, -uv.y, -1.0); break; // NEGATIVE_Z
	}
	return normalize(dir);
}

void main() {
	ivec3 storePos = ivec3(gl_GlobalInvocationID);
	ivec2 size = imageSize(irradianceMap).xy;
	if (storePos.x >= size.x || storePos.y >= size.y) return;

	vec2 uv = (vec2(storePos.xy) + 0.5) / vec2(size);
	vec3 normal = getDirection(storePos.z, uv);

	vec3 irradiance = vec3(0.0);
	vec3 up	= vec3(0.0, 1.0, 0.0);
	vec3 right = normalize(cross(up, normal));
	up		 = normalize(cross(normal, right));

	float sampleDelta = 0.025;
	float nrSamples = 0.0;

	for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) {
		for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) {
			vec3 tangentSample = vec3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
			vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * normal;

			irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
			nrSamples++;
		}
	}
	irradiance = PI * irradiance * (1.0 / float(nrSamples));

	imageStore(irradianceMap, storePos, vec4(irradiance, 1.0));
}