/* clang-format off */
[vertex]

layout(location = 0) in highp vec2 vertex;
/* clang-format on */

layout(location = 4) in highp vec2 uv;

out highp vec2 uv_interp;

void main() {

	uv_interp = uv;
	gl_Position = vec4(vertex, 0, 1);
}

/* clang-format off */
[fragment]

precision highp float;
/* clang-format on */
precision highp int;

#ifdef USE_SOURCE_PANORAMA
uniform sampler2D source_panorama; //texunit:0
#endif

#ifdef USE_SOURCE_DUAL_PARABOLOID_ARRAY
uniform sampler2DArray source_dual_paraboloid_array; //texunit:0
uniform int source_array_index;
#endif

#if !defined(USE_SOURCE_DUAL_PARABOLOID_ARRAY) && !defined(USE_SOURCE_PANORAMA)
uniform samplerCube source_cube; //texunit:0
#endif

uniform int face_id;
uniform float roughness;
in highp vec2 uv_interp;

layout(location = 0) out vec4 frag_color;

#define M_PI 3.14159265359

vec3 texelCoordToVec(vec2 uv, int faceID) {
	mat3 faceUvVectors[6];
	/*
	// -x
	faceUvVectors[1][0] = vec3(0.0, 0.0, 1.0);  // u -> +z
	faceUvVectors[1][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[1][2] = vec3(-1.0, 0.0, 0.0); // -x face

	// +x
	faceUvVectors[0][0] = vec3(0.0, 0.0, -1.0); // u -> -z
	faceUvVectors[0][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[0][2] = vec3(1.0, 0.0, 0.0);  // +x face

	// -y
	faceUvVectors[3][0] = vec3(1.0, 0.0, 0.0);  // u -> +x
	faceUvVectors[3][1] = vec3(0.0, 0.0, -1.0); // v -> -z
	faceUvVectors[3][2] = vec3(0.0, -1.0, 0.0); // -y face

	// +y
	faceUvVectors[2][0] = vec3(1.0, 0.0, 0.0);  // u -> +x
	faceUvVectors[2][1] = vec3(0.0, 0.0, 1.0);  // v -> +z
	faceUvVectors[2][2] = vec3(0.0, 1.0, 0.0);  // +y face

	// -z
	faceUvVectors[5][0] = vec3(-1.0, 0.0, 0.0); // u -> -x
	faceUvVectors[5][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[5][2] = vec3(0.0, 0.0, -1.0); // -z face

	// +z
	faceUvVectors[4][0] = vec3(1.0, 0.0, 0.0);  // u -> +x
	faceUvVectors[4][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[4][2] = vec3(0.0, 0.0, 1.0);  // +z face
	*/

	// -x
	faceUvVectors[0][0] = vec3(0.0, 0.0, 1.0); // u -> +z
	faceUvVectors[0][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[0][2] = vec3(-1.0, 0.0, 0.0); // -x face

	// +x
	faceUvVectors[1][0] = vec3(0.0, 0.0, -1.0); // u -> -z
	faceUvVectors[1][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[1][2] = vec3(1.0, 0.0, 0.0); // +x face

	// -y
	faceUvVectors[2][0] = vec3(1.0, 0.0, 0.0); // u -> +x
	faceUvVectors[2][1] = vec3(0.0, 0.0, -1.0); // v -> -z
	faceUvVectors[2][2] = vec3(0.0, -1.0, 0.0); // -y face

	// +y
	faceUvVectors[3][0] = vec3(1.0, 0.0, 0.0); // u -> +x
	faceUvVectors[3][1] = vec3(0.0, 0.0, 1.0); // v -> +z
	faceUvVectors[3][2] = vec3(0.0, 1.0, 0.0); // +y face

	// -z
	faceUvVectors[4][0] = vec3(-1.0, 0.0, 0.0); // u -> -x
	faceUvVectors[4][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[4][2] = vec3(0.0, 0.0, -1.0); // -z face

	// +z
	faceUvVectors[5][0] = vec3(1.0, 0.0, 0.0); // u -> +x
	faceUvVectors[5][1] = vec3(0.0, -1.0, 0.0); // v -> -y
	faceUvVectors[5][2] = vec3(0.0, 0.0, 1.0); // +z face

	// out = u * s_faceUv[0] + v * s_faceUv[1] + s_faceUv[2].
	vec3 result = (faceUvVectors[faceID][0] * uv.x) + (faceUvVectors[faceID][1] * uv.y) + faceUvVectors[faceID][2];
	return normalize(result);
}

vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N) {
	float a = Roughness * Roughness; // DISNEY'S ROUGHNESS [see Burley'12 siggraph]

	// Compute distribution direction
	float Phi = 2.0 * M_PI * Xi.x;
	float CosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y));
	float SinTheta = sqrt(1.0 - CosTheta * CosTheta);

	// Convert to spherical direction
	vec3 H;
	H.x = SinTheta * cos(Phi);
	H.y = SinTheta * sin(Phi);
	H.z = CosTheta;

	vec3 UpVector = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
	vec3 TangentX = normalize(cross(UpVector, N));
	vec3 TangentY = cross(N, TangentX);

	// Tangent to world space
	return TangentX * H.x + TangentY * H.y + N * H.z;
}

// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html
float GGX(float NdotV, float a) {
	float k = a / 2.0;
	return NdotV / (NdotV * (1.0 - k) + k);
}

// http://graphicrants.blogspot.com.au/2013/08/specular-brdf-reference.html
float G_Smith(float a, float nDotV, float nDotL) {
	return GGX(nDotL, a * a) * GGX(nDotV, a * a);
}

float radicalInverse_VdC(uint bits) {
	bits = (bits << 16u) | (bits >> 16u);
	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
	return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}

vec2 Hammersley(uint i, uint N) {
	return vec2(float(i) / float(N), radicalInverse_VdC(i));
}

#ifdef LOW_QUALITY

#define SAMPLE_COUNT 64u

#else

#define SAMPLE_COUNT 512u

#endif

uniform bool z_flip;

#ifdef USE_SOURCE_PANORAMA

vec4 texturePanorama(vec3 normal, sampler2D pano) {

	vec2 st = vec2(
			atan(normal.x, normal.z),
			acos(normal.y));

	if (st.x < 0.0)
		st.x += M_PI * 2.0;

	st /= vec2(M_PI * 2.0, M_PI);

	return textureLod(pano, st, 0.0);
}

#endif

#ifdef USE_SOURCE_DUAL_PARABOLOID_ARRAY

vec4 textureDualParaboloidArray(vec3 normal) {

	vec3 norm = normalize(normal);
	norm.xy /= 1.0 + abs(norm.z);
	norm.xy = norm.xy * vec2(0.5, 0.25) + vec2(0.5, 0.25);
	if (norm.z < 0.0) {
		norm.y = 0.5 - norm.y + 0.5;
	}
	return textureLod(source_dual_paraboloid_array, vec3(norm.xy, float(source_array_index)), 0.0);
}

#endif

void main() {

#ifdef USE_DUAL_PARABOLOID

	vec3 N = vec3(uv_interp * 2.0 - 1.0, 0.0);
	N.z = 0.5 - 0.5 * ((N.x * N.x) + (N.y * N.y));
	N = normalize(N);

	if (z_flip) {
		N.y = -N.y; //y is flipped to improve blending between both sides
		N.z = -N.z;
	}

#else
	vec2 uv = (uv_interp * 2.0) - 1.0;
	vec3 N = texelCoordToVec(uv, face_id);
#endif
	//vec4 color = color_interp;

#ifdef USE_DIRECT_WRITE

#ifdef USE_SOURCE_PANORAMA

	frag_color = vec4(texturePanorama(N, source_panorama).rgb, 1.0);
#endif

#ifdef USE_SOURCE_DUAL_PARABOLOID_ARRAY

	frag_color = vec4(textureDualParaboloidArray(N).rgb, 1.0);
#endif

#if !defined(USE_SOURCE_DUAL_PARABOLOID_ARRAY) && !defined(USE_SOURCE_PANORAMA)

	N.y = -N.y;
	frag_color = vec4(texture(N, source_cube).rgb, 1.0);
#endif

#else

	vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);

	for (uint sampleNum = 0u; sampleNum < SAMPLE_COUNT; sampleNum++) {
		vec2 xi = Hammersley(sampleNum, SAMPLE_COUNT);

		vec3 H = ImportanceSampleGGX(xi, roughness, N);
		vec3 V = N;
		vec3 L = (2.0 * dot(V, H) * H - V);

		float ndotl = clamp(dot(N, L), 0.0, 1.0);

		if (ndotl > 0.0) {
#ifdef USE_SOURCE_PANORAMA
			sum.rgb += texturePanorama(L, source_panorama).rgb * ndotl;
#endif

#ifdef USE_SOURCE_DUAL_PARABOLOID_ARRAY

			sum.rgb += textureDualParaboloidArray(L).rgb * ndotl;
#endif

#if !defined(USE_SOURCE_DUAL_PARABOLOID_ARRAY) && !defined(USE_SOURCE_PANORAMA)
			L.y = -L.y;
			sum.rgb += textureLod(source_cube, L, 0.0).rgb * ndotl;
#endif
			sum.a += ndotl;
		}
	}
	sum /= sum.a;

	frag_color = vec4(sum.rgb, 1.0);

#endif
}
