#version 330 core
#define PI 3.1415926535897932384626433832795
#define MAX_JOINTS 128

// BEGIN --- Spherical Harmonics ---
	const float C1 = 0.429043;
	const float C2 = 0.511664;
	const float C3 = 0.743125;
	const float C4 = 0.886227;
	const float C5 = 0.247708;
	const vec3 L00 = vec3(0.32, 0.31, 0.35);
	const vec3 L1m1 = vec3(0.37, 0.37, 0.43);
	const vec3 L10 = vec3(0.0, 0.0, 0.0);
	const vec3 L11 = vec3(-0.01, -0.01, -0.01);
	const vec3 L2m2 = vec3(-0.1, -0.01, -0.01);
	const vec3 L2m1 = vec3(-0.02, -0.02, -0.03);
	const vec3 L20 = vec3(-0.28, -0.28, -0.32);
	const vec3 L21 = vec3(0.0, 0.0, 0.0);
	const vec3 L22 = vec3(-0.24, -0.24, -0.28);

	vec3 SphericalHarmonicLighting(vec3 fragmentNormalWorldSpace)
	{ 
		return  C1 * L22 *(fragmentNormalWorldSpace.x * fragmentNormalWorldSpace.x - fragmentNormalWorldSpace.y * fragmentNormalWorldSpace.y) +
				C3 * L20 * fragmentNormalWorldSpace.z * fragmentNormalWorldSpace.z +
				C4 * L00 -
				C5 * L20 +
				2.0 * C1 * L2m2 * fragmentNormalWorldSpace.x * fragmentNormalWorldSpace.y +
				2.0 * C1 * L21 * fragmentNormalWorldSpace.x * fragmentNormalWorldSpace.z +
				2.0 * C1 * L2m1 * fragmentNormalWorldSpace.y * fragmentNormalWorldSpace.z +
				2.0 * C2 * L11 * fragmentNormalWorldSpace.x +
				2.0 * C2 * L1m1 * fragmentNormalWorldSpace.y +
				2.0 * C2 * L10 * fragmentNormalWorldSpace.z;
	}
// END --- Spherical Harmonics ---

// BEGIN --- Seamless Cubemap Filtering ---
	// This is intended to be used with cubemaps generated with the "Stretch" mode of ModifiedCubemapGen,
	// but it can also work with the "Warp" method as well.
	vec3 fix_cube_lookup(vec3 v, float exp2MipMapIndex, float baseMipMapSize)
	{ 
		float scale = 1.0 - exp2MipMapIndex / baseMipMapSize;
		float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
		if (abs(v.x) != M) v.x *= scale;
		if (abs(v.y) != M) v.y *= scale;
		if (abs(v.z) != M) v.z *= scale;
		return v;
	}
	
	float cubeMipFromRoughness(float linearRoughness, float roughestMip)
	{
		return linearRoughness * roughestMip;
	}
// END --- Seamless Cubemap Filtering

// BEGIN --- Unreal Engine 4 ---
	// BEGIN --- Analytic Lights ---
		vec3 LambertianDiffuse_UE4(vec3 albedo)
		{
			return albedo / PI;
		}
		
		float D_GGX_UE4(float NdotH, float linearRoughness)
		{
			float linearRoughness2 = linearRoughness * linearRoughness;
			float d = (NdotH * linearRoughness2 - NdotH) * NdotH + 1;
			return linearRoughness2 / (PI * d * d);
		}

		float G_SchlickSmith_UE4(float NdotV, float NdotL, float linearRoughness)
		{
			float k = linearRoughness * 0.5;
			float visV = NdotV * (1 - k) + k;
			float visL = NdotL * (1 - k) + k;
			return 0.25 / (visV + visL);
		}

		vec3 F_Schlick_UE4(vec3 specularColor, float u)
		{
			float fresnelFactor = pow(1 - u, 5);
			return  clamp( 50.0 * specularColor.g, 0.0, 1.0) * fresnelFactor + (1 - fresnelFactor) * specularColor;
		}
	// END --- Analytic Lights ---
	
	// BEGIN --- Evironment Lights ---
		vec3 EnvBRDFApprox(vec3 specularColor, float linearRoughness, float NdotV)
		{
			vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
			vec4 c1 = vec4( 1.0, 0.0425, 1.04, -0.04);
			vec4 r = linearRoughness * c0 + c1;
			float a004 = min( r.x * r.x, exp2( -9.28 * NdotV ) ) * r.x + r.y;
			vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
			AB.y *= clamp(50.0 * specularColor.g, 0.0, 1.0);
			return specularColor * AB.x + AB.y;
		}
		
		vec3 EnvBRDFApproxNonmetal(float linearRoughness, float NdotV)
		{
			// Same as EnvBRDFApprox( 0.04, Roughness, NoV )
			const vec2 c0 = vec2(-1.0, -0.0275);
			const vec2 c1 = vec2(1.0, 0.0425);
			vec2 r = vec2(linearRoughness * c0 + c1);
			return vec3(min( r.x * r.x, exp2( -9.28 * NdotV ) ) * r.x + r.y);
		}
	// END --- Evironment Lights ---
	
	// BEGIN --- Tonemap and Gamma correction ---
		const float shoulderStrength = 0.22;
		const float linearStrength = 0.30;
		const float linearAngle = 0.10;
		const float toeStrength = 0.20;
		const float toeNumerator = 0.01;
		const float toeDenominator = 0.30;
		const float linearWhitePointValue = 11.2;
		vec3 toneMap(vec3 linearColor)
		{
			return ((linearColor * (shoulderStrength * linearColor + linearAngle * linearStrength) +  toeStrength * toeNumerator) / (linearColor * (shoulderStrength * linearColor + linearStrength) + toeStrength * toeDenominator)) - toeNumerator / toeDenominator;
		}
	
		vec3 tonemapAndGammaCorrect(vec3 linearColor)
		{	
			vec3 linearWhite = vec3(linearWhitePointValue);
			vec3 finalColor = toneMap(linearColor) / toneMap(linearWhite);
			return finalColor;
		}
	// END --- Tonemap and Gamma correction ---
// END --- Unreal Engine 4 ---

// BEGIN --- Normal Encode ---
	vec4 encodeNormal(vec3 viewSpaceNormal)
	{
		const float scale_rcp = 1.0 / 1.7777;
		vec2 encodedNormal = viewSpaceNormal.xy / (viewSpaceNormal.z + 1);
		encodedNormal *= scale_rcp;
		encodedNormal = encodedNormal * 0.5 + 0.5;
		return vec4(encodedNormal, 0.0, 1.0);
	}
	
	// Credit: http://www.thetenthplanet.de/archives/1180
	vec3 tangentSpaceToObjectSpaceNormal(vec3 position, vec2 UV, vec3 objectSpaceNormal, vec3 tangentSpaceNormal)
	{
		vec3 dFdx_pos = dFdx(position);
		vec3 dFdy_pos = dFdy(position);
		vec2 dFdx_UV = dFdx(UV);
		vec2 dFdy_UV = dFdy(UV);

		// solve the linear system
		vec3 dp2perp = cross(dFdy_pos, objectSpaceNormal);
		vec3 dp1perp = cross(objectSpaceNormal, dFdx_pos);
		vec3 tangent = dp2perp * dFdx_UV.x + dp1perp * dFdy_UV.x;
		vec3 biTangent = dp2perp * dFdx_UV.y + dp1perp * dFdy_UV.y;

		// construct a scale-invariant frame 
		float invmax = inversesqrt(max(dot(tangent, tangent), dot(biTangent, biTangent)));
		mat3 TBN = mat3(tangent * invmax, biTangent * invmax, objectSpaceNormal);
		
		return normalize(TBN * tangentSpaceNormal);
	}
// END --- Normal Encode --- 

// BEGIN --- Textures --- 
	uniform sampler2D iTexture0; // Albedo
	uniform sampler2D iTexture1; // Specular
	uniform sampler2D iTexture2; // Smoothness
	uniform sampler2D iTexture3; // Normal
	uniform sampler2D iTexture4; // Height
	uniform sampler2D iTexture5; // Occlusion
	uniform sampler2D iTexture6; // Emission
	uniform sampler2D iTexture7; // Detail Mask
	uniform sampler2D iTexture8; // Detail Albedo
	uniform sampler2D iTexture9; // Detail Normal
	uniform samplerCube iCubeTexture0; // Skybox
	uniform samplerCube iCubeTexture1; // Skybox Radiance
	uniform samplerCube iCubeTexture2; // Skybox Irradiance
// END --- Textures --- 

// BEGIN --- Uniforms --- 

	// BEGIN --- Frame --- 
		uniform vec4 iLeftEye;
		uniform vec4 iMouse;
		uniform vec4 iResolution;
		uniform vec4 iGlobalTime;
	// END --- Frame --- 
	
	// BEGIN --- Room --- 
		uniform mat4 iMiscRoomData;
		uniform vec4 iPlayerPosition;
		uniform vec4 iUseClipPlane;
		uniform vec4 iClipPlane;
		uniform vec4 iFogEnabled;
		uniform vec4 iFogMode;
		uniform vec4 iFogDensity;
		uniform vec4 iFogStart;
		uniform vec4 iFogEnd;
		uniform vec4 iFogCol;
		uniform mat4 iRoomMatrix;
	// END --- Room --- 
	
	// BEGIN --- Object --- 
		uniform mat4 iMiscObjectData;
		uniform vec4 iConstColour;
		uniform vec4 iChromaKeyColour;
		uniform vec4 iUseLighting;
		uniform vec4 iObjectPickID;
		uniform mat4 iModelMatrix;
		uniform mat4 iViewMatrix;
		uniform mat4 iProjectionMatrix;
		uniform mat4 iInverseViewMatrix;
		uniform mat4 iModelViewMatrix;
		uniform mat4 iModelViewProjectionMatrix;
		uniform mat4 iTransposeInverseModelMatrix;
		uniform mat4 iTransposeInverseModelViewMatrix;
		uniform vec4 iBlend;
		uniform vec4 iUseSkelAnim;
		uniform mat4 iSkelAnimJoints[MAX_JOINTS];
	// END --- Object --- 
	
	// BEGIN --- Material --- 
	uniform vec4 iAmbient;
	uniform vec4 iDiffuse;
	uniform vec4 iSpecular;
	uniform vec4 iShininess;
	uniform vec4 iEmission;
	uniform vec4 iUseTexture[4];
	// BEGIN --- Material --- 
	
// END --- Uniforms --- 

// BEGIN --- Input Interpolants --- 
	smooth in vec3 iPosition;
	smooth in vec3 iPositionWorld;
	smooth in vec3 iPositionCamera;
	smooth in vec3 iNormal;
	smooth in vec3 iNormalWorld;
	smooth in vec3 iNormalCamera;
	smooth in vec2 iTexCoord0;
	smooth in vec2 iTexCoord1;
// END --- Input Interpolants --- 

// BEGIN --- Framebuffer Outputs --- 
	layout(location = 0) out vec4 out_color;
	layout(location = 1) out uvec4 out_objectID;
	layout(location = 2) out vec4 out_encoded_view_space_normal;
// END --- Framebuffer Outputs --- 



// BEGIN addon shit
vec3 rgb2hsv(vec3 c)
{
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
 
    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

vec3 lerpHsv(vec3 a1, vec3 b1, float t)
{
	vec3 a = a1;
	vec3 b = b1;
	// Hue interpolation
	float h;
	float d = b.x - a.x;
	if (a.x > b.x)
	{
		// Swap (a.h, b.h)
		float h3 = b.x;
		b.x = a.x;
		a.x = h3;
 
		d = -d;
		t = 1 - t;
	}
 
	if (d > 0.5) // 180deg
	{
		a.x = a.x + 1; // 360deg
		h = mod(( a.x + t * (b.x - a.x) ) , 1); // 360deg
	}
	if (d <= 0.5) // 180deg
	{
		h = a.x + t * d;
	}
	return vec3(h, a.y + t * (b.y-a.y), a.z + t * (b.z-a.z));
}
vec3 getBeamColor(int offset, int timeoffset) {

	vec3 baseColor;
	float color;
	if (floor(mod(iGlobalTime.x,20)) < 10) { // cool
		color = floor(mod(iGlobalTime.x / (2+timeoffset)+offset,5));
		if (color == 0) {
			baseColor = vec3(0,1,0); }
		else if (color == 1) {
			baseColor = vec3(0,0,1); }
		else if (color == 2) {
			baseColor = vec3(0.3,0,1); }
		else if (color == 3) {
			baseColor = vec3(0,0.3,1); }
		else if (color == 4) {
			baseColor = vec3(0,1,1); }
		else if (color == 5) {
			baseColor = vec3(0,1,0.3); }
		return baseColor;
	}
	else { // warm
		color = floor(mod(iGlobalTime.x / (2+timeoffset)+offset,5));
		if (color == 0) {
			baseColor = vec3(1,0,0); }
		else if (color == 1) {
			baseColor = vec3(1,0.3,0); }
		else if (color == 2) {
			baseColor = vec3(0.5,1,0.0); }
		else if (color == 3) {
			baseColor = vec3(1,0,1); }
		else if (color == 4) {
			baseColor = vec3(1,0,0); }
		else if (color == 5) {
			baseColor = vec3(1,0,0.3); }
		return baseColor;
	}
}
// END addon shit



void main(void)
{	
	// BEGIN --- Clipping plane test ---
		if (iUseClipPlane.x == 1 
			&& dot(iPositionWorld, iClipPlane.xyz) < iClipPlane.w)
		{
			discard;
		}
	// END --- Clipping plane test ---
	
	// BEGIN --- Conditional Discards ---
		if (iUseTexture[0].x == 1.0) 
		{
			vec4 albedoTexel = texture(iTexture0, iTexCoord0.xy);
			if (iChromaKeyColour.a > 0.9  && albedoTexel.rgb == iChromaKeyColour.rgb)
			{
				discard;
			}
			
			if (albedoTexel.a < 0.05)
			{
				discard;
			}
		}
	// END --- Chroma key test ---
	
	// BEGIN --- Emissive ---
		vec4 emission = iEmission;
		if (iUseTexture[1].z == 1.0)
		{
			emission = vec4(texture(iTexture6, iTexCoord0.xy).rgb, 0.0);
		}
	// END --- Emissive ---
	
	// BEGIN --- Occlusion ---
		vec4 occlusion = iAmbient;
		if (iUseTexture[1].y == 1.0)
		{
			occlusion = vec4(texture(iTexture5, iTexCoord1.xy).rgb, 1.0);
		}
	// END --- Occlusion ---

	// BEGIN --- Lighting ---		
		// BEGIN --- Common Lighting variables ---
			float roughness = 0.0;
			float linearRoughness = 0.0;
			float roughnessFactorGeometric = 0.0;
			float roughnessFactorTexture = 0.0;
			vec3 indirectDiffuseLight = vec3(0.0);
			vec3 indirectSpecularLight = vec3(0.0);
			vec3 f0 = vec3(0.0);
			vec3 envBRDF = vec3(0.0);
			vec3 diffuse = vec3(0.0);
			vec3 specular = vec3(0.0);
			float D = 0.0;
			float G = 0.0;
			vec3 F = vec3(0.0);
			vec3 indirectDiffuse = vec3(0.0);
			vec3 indirectSpecular = vec3(0.0);
			vec3 fragToLightViewSpace = vec3(0.0);
			float lightDistance = 0.0;
			float lightDistanceSquared = 0.0;
			float distanceAttenuation = 0.0;
			vec3 normalizedViewNormal = vec3(0.0);
			float normalLength = 0.0;
			vec3 N = vec3(0.0); // normalizedWorldNormal, shorthand
			vec3 L = vec3(0.0);
			vec3 V = vec3(0.0);
			vec3 H = vec3(0.0);
			float NdotL = 0.0;
			float NdotV = 0.0;
			float NdotH = 0.0;
			float VdotH = 0.0;
			float normalizedMipMapIndex = 0.0;
			vec3 objectSpaceNormal = vec3(0.0);
			vec3 normalizedNormalTexel = vec3(0.0);
		// END --- Common Lighting variables ---
			
		// BEGIN --- Normal mapping ---
			// If we have a normal map
			if (iUseTexture[0].w == 1.0)
			{
				// Fetch object space normal [0.0,1.0] range per channel
				vec3 normalTexel = texture(iTexture3, iTexCoord0.xy).rgb;
				// Store the length for roughness modulation then normalize it
				normalLength = length(normalTexel);
				normalizedNormalTexel = normalTexel / normalLength;
				// Convert normal into [-1.0, 1.0] range per channel
				normalizedNormalTexel = normalTexel * 255.0 / 127.0 - 128.0 / 127.0;
				// Convert normal into object-space
				objectSpaceNormal = tangentSpaceToObjectSpaceNormal(iPosition, iTexCoord0.xy, iNormal, normalizedNormalTexel);
				//mat3 tbnMatrix = mat3(normalize(iTangent), normalize(iBitangent), normalize(iNormal));
				//mat3 tbnMatrix = mat3(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), normalize(iNormal));
				//objectSpaceNormal = tbnMatrix * normalizedNormalTexel;
				// Convert normal into view-space
				normalizedViewNormal = normalize(mat3(iTransposeInverseModelViewMatrix) * objectSpaceNormal);
				N = normalize(mat3(iInverseViewMatrix) * normalizedViewNormal);
			}
			else
			{
				// Store the length for roughness modulation then normalize
				normalLength = length(iNormalCamera);
				normalizedViewNormal = iNormalCamera / normalLength;
				N = normalize(iNormalWorld);
			}
			float ddx_ddy = length(fwidth(normalizedViewNormal));
			roughnessFactorGeometric = pow(clamp(ddx_ddy, 0.0, 1.0), 2) * 0.03;
		// END --- Normal mapping ---
						
		// BEGIN --- Albedo ---	
			vec4 accumulatedLight = vec4(0.0, 0.0, 0.0, 0.0);
			vec4 albedo = vec4(0.0, 0.0, 0.0, 0.0);
			if (iUseTexture[0].x == 1.0)
			{
				albedo = pow(texture(iTexture0, iTexCoord0.xy), vec4(2.2, 2.2, 2.2, 1.0));
			}
			else
			{
				albedo = iDiffuse;
			}
		// END --- Albedo ---	
		
		if (iUseLighting.x == 1.0)
		{
		// BEGIN --- Roughness mapping ---
			// Roughness2, roughesness squared, (Perceptually linear)
			// If we have a smoothness map
			if (iUseTexture[0].z == 1.0)
			{
				// Convert from [0,1] smoothness stored in the texture
				roughness = 1.0 - texture(iTexture2, iTexCoord0.xy).r;
			}
			else
			{
				if (iShininess.r < 511.0)
				{
					// Remap [1,511] into [4,2044] then to [2,2042] then map that into [0,1] linear smoothness
					roughness = (log2(clamp(4 * iShininess.r - 2, 2, 2048)) - 1.0) * 0.1;
					// Convert from [0,1] smoothness to linear roughness
					roughness = 1.0 - roughness;
				}
				else
				{
					roughness = 0.0;
				}
			}
			// Square the roughness to make it perceptually linear
			roughness = roughness + roughnessFactorGeometric;
			linearRoughness = roughness * roughness + roughnessFactorGeometric;
			roughness = clamp(roughness, 0.001, 1.0);
			linearRoughness = clamp(linearRoughness, 0.001, 1.0);
			
		// END --- Roughness mapping ---
			
		// BEGIN --- Specular ---
			if (iUseTexture[0].y == 1.0)
			{
				f0 = pow(texture(iTexture1, iTexCoord0.xy).rgb, vec3(2.2));
			}
			else
			{
				f0 = iSpecular.rgb;
			}
		// END --- Specular ---
		
		// BEGIN --- Light Accumulation ---
			accumulatedLight = vec4(0.0, 0.0, 0.0, albedo.a);
			//vec3 lightContribution = vec3(0.0);
			// BEGIN --- Hard-coded white light floating just at camera ---
				//const vec3 lightColor = vec3(1.0, 1.0, 1.0);
				//const float lightIntensity = 7.065;
				//vec3 lightWorldSpacePosition = vec3(iRoomMatrix * vec4(-10.5, 4.3, -10.5, 1.0));
				//vec3 lightWorldSpacePosition = vec3(iInverseViewMatrix * vec4(0.0, 0.0, 0.0, 1.0));
				//vec3 fragToLightWorldSpace = lightWorldSpacePosition - iPositionWorld;
				//lightWorldSpacePosition = lightWorldSpacePosition - iPositionWorld;
			// BEGIN --- Hard-coded white light floating just at camera ---
			
			// BEGIN --- Light Attenuation ---
				//lightDistance = length(lightWorldSpacePosition);
				//lightDistanceSquared = lightDistance * lightDistance;
				// L is the direction from the fragment to the light in world space
				//L = lightWorldSpacePosition / lightDistance;
				//distanceAttenuation = 1 / (lightDistanceSquared + 1);
				//vec3 attenuatedLight = lightColor * lightIntensity * distanceAttenuation;
			// END --- Light Attenuation ---
		
			// START --- Surface Lighting ---
				V = mat3(iInverseViewMatrix) * normalize(-iPositionCamera); // FragToView WorldSpace
				//H = normalize(V + L);
				//NdotL = clamp(dot(N, L), 0.0, 1.0);
				NdotV = clamp(dot(N, V), 0.0, 1.0);
				//NdotH = clamp(dot(N, H), 0.0, 1.0);
				//VdotH = clamp(dot(V, H), 0.0, 1.0);
				//D = D_GGX_UE4(NdotH, linearRoughness);
				//G = G_SchlickSmith_UE4(NdotV, NdotL, linearRoughness);
				//F = F_Schlick_UE4(f0, VdotH);
				//lightContribution = attenuatedLight * NdotL;
				//diffuse = LambertianDiffuse_UE4(albedo.rgb) * lightContribution;
				//specular = D * G * F * lightContribution;
				//accumulatedLight.rgb += diffuse + specular;
			// END --- Surface Lighting ---
		// END --- Light Accumulation ---
			
		// BEGIN --- Indirect Diffuse ---
			vec3 roomSpaceNormal = inverse(mat3(iRoomMatrix)) * N;
			vec3 fixedLookup;
			if(iUseTexture[3].x == 0.0)
			{
				indirectDiffuseLight = max(SphericalHarmonicLighting(normalize(roomSpaceNormal)).ggg, vec3(0.10, 0.10, 0.10));
			}
			else
			{
				fixedLookup = roomSpaceNormal;
				fixedLookup.z = -fixedLookup.z;
				indirectDiffuseLight = pow(textureLod(iCubeTexture2, fixedLookup, 0.0).rgb, vec3(2.2));
			}
			vec3 hsv = rgb2hsv(indirectDiffuseLight.rgb);
			vec3 hsv_copy = rgb2hsv(getBeamColor(0,0));
			vec3 hsv_new = vec3(lerpHsv(hsv, hsv_copy, 1.).x,hsv.yz);
			indirectDiffuse = albedo.rgb * hsv2rgb(hsv_new);
		// END --- Indirect Diffuse ---
		
		// BEGIN --- Indirect Specular ---
			float NUM_MIP_LEVELS = 9.0;
			float BASE_MIP_SIZE = 256.0;
			float mipMapIndex = cubeMipFromRoughness(roughness, NUM_MIP_LEVELS - 1);
			mipMapIndex = clamp(mipMapIndex, 0.0, NUM_MIP_LEVELS - 1);
			float exp2MipMapIndex = exp2(mipMapIndex);
			vec3 worldSpaceReflection = reflect(-V, N);
			vec3 roomSpaceReflection = inverse(mat3(iRoomMatrix)) * worldSpaceReflection;
			fixedLookup = roomSpaceReflection;
			//fixedLookup = fix_cube_lookup(roomSpaceReflection, exp2MipMapIndex, BASE_MIP_SIZE);
			fixedLookup.z = -fixedLookup.z;
			if(iUseTexture[2].w == 0.0)
			{
				indirectSpecularLight = max(SphericalHarmonicLighting(roomSpaceReflection).ggg, vec3(0.05, 0.05, 0.05));
			}
			else
			{
				if (linearRoughness == 0.001)
				{
					indirectSpecularLight = pow(textureLod(iCubeTexture1, fixedLookup, 0.0).rgb, vec3(2.2));
				}
				else
				{
					indirectSpecularLight = pow(textureLod(iCubeTexture1, fixedLookup, mipMapIndex).rgb, vec3(2.2));
				}
			}
			envBRDF = EnvBRDFApprox(f0, linearRoughness, NdotV);
			indirectSpecular = envBRDF * indirectSpecularLight;
			
			hsv = rgb2hsv(occlusion.rgb);
			hsv_copy = rgb2hsv(getBeamColor(0,0));
			hsv_new = vec3(lerpHsv(hsv, hsv_copy, 1.).x,hsv.yz);
			occlusion.rgb = hsv2rgb(hsv_new);
			
			accumulatedLight.rgb += (indirectDiffuse + indirectSpecular) * occlusion.rgb * (vec3(0.5,0.5,0.5)+getBeamColor(0,0));
		// END --- Indirect Specular ---
		}
	// END --- Lighting ---

	
	
	// BEGIN --- Lighting Composition ---
		vec4 lightingResult = iConstColour;
		if (iUseLighting.x == 1.0)
		{
			lightingResult *= (accumulatedLight + emission);
		}
		else
		{
			lightingResult *= (albedo * occlusion + emission);
		}
	// END --- Lighting Composition  ---
	
	// BEGIN --- Fog ---
		if (iFogEnabled.x == 1.0)
		{
			//compute length from eye to fragment
			float fragDistance = length(iPositionCamera);   
			//compute blend value
			float fogInterpolator;
			if (iFogMode.x == 0.0)
			{
				fogInterpolator = (iFogEnd.x - fragDistance) / (iFogEnd.x - iFogStart.x);
			}
			else if (iFogMode.x == 1.0)
			{
				fogInterpolator = exp(-iFogDensity.x * fragDistance);
			}
			else
			{
				fogInterpolator = exp(-(iFogDensity.x * fragDistance)*(iFogDensity.x * fragDistance));
			}
			fogInterpolator = clamp(fogInterpolator, 0.0, 1.0);
			lightingResult = mix(iFogCol, lightingResult, fogInterpolator);
		}
	// END --- Fog ---
	
	// BEGIN --- Temporal Noise --- Disabled until I find a better noise generator
		/*vec3 dither = vec3(dot(vec2(171.0, 231.0), gl_FragCoord.xy + iGlobalTime.xy));
		dither = fract(dither / vec3(103.0, 71.0, 97.0)) - vec3(0.5, 0.5, 0.5);
		dither *= 0.0375;
		lightingResult += vec4(dither, 0.0);*/
	// END --- Temporal Noise ---
	
	// START --- Fragment Shader Outputs ---
		// Pre-multiply the output by the alpha for correct blending
		out_color.rgb *= out_color.a;
		out_color = lightingResult;
		//out_color = vec4(vec3(indirectSpecularLight), 1.0);
		/*if((gl_FragCoord.x / (iResolution.x * 0.5)) <= 0.25)
		{
			out_color = vec4(pow(texture(iTexture0, iTexCoord0.xy).rgb, vec3(2.2)), 1.0);
		}
		else if((gl_FragCoord.x / (iResolution.x * 0.5)) <= 0.5)
		{
			out_color = vec4(pow(texture(iTexture1, iTexCoord0.xy).rgb, vec3(2.2)), 1.0);
		}
		else if((gl_FragCoord.x / (iResolution.x * 0.5)) <= 0.75)
		{
			// This texture wouldn't be sRGB but I treat it like it is so that it'll match the colors
			// you would see when viewing the texture in non-sRGB mode in normal desktop viewers
			out_color = vec4(pow(texture(iTexture2, iTexCoord0.xy).rgb, vec3(2.2)), 1.0);
		}
		else if((gl_FragCoord.x / (iResolution.x * 0.5)) <= 1.0)
		{
			// This texture wouldn't be sRGB but I treat it like it is so that it'll match the colors
			// you would see when viewing the texture in non-sRGB mode in normal desktop viewers
			out_color = vec4(pow(texture(iTexture3, iTexCoord0.xy).rgb, vec3(2.2)), 1.0);
		}*/
		out_objectID = uvec4(uint(iObjectPickID), uint(round(fract(iTexCoord0.x) * 65535.0)), uint(round(fract(iTexCoord0.y) * 65535.0)), 65535);
		out_encoded_view_space_normal = encodeNormal(normalizedViewNormal);
	// END --- Fragment Shader Outputs ---
}