

float spotShape(float r){
	float shape = curve(saturate(r * 2.0 - (2.0 - FLASHLIGHT_FOV)));
	shape += curve(saturate(r * 1.25 - (1.0 - FLASHLIGHT_FOV))) * 0.01;

	const float ringSize = 8.0 / FLASHLIGHT_FOV;
	shape *= 1.0 - curve(saturate(1.0 - abs(r * ringSize - ringSize + 1.0))) * 0.25;

	return shape;
}


float TorchScreenSpaceShadow(vec3 viewPos, vec3 viewDir, vec3 normal, vec3 shadowDir){
	shadowDir.z = max(shadowDir.z, 1e-5);

	float rayLength = -0.04 * viewPos.z / shadowDir.z;
	vec3 viewRayDir = shadowDir * rayLength;

	vec3 start = viewPos;

	float pixelScale = max(UNIFORM_PIXEL_SIZE.x, UNIFORM_PIXEL_SIZE.y);
	float NdotL = saturate(dot(shadowDir, normal));

	float fov = atan(1.0 / gbufferProjection0.y) * 360.0 * rPI;
	start += viewRayDir * (pixelScale * max(0.04 / max(dot(normal, -viewDir), 0.1), 1e3));
	start += normal * (8e-3 * fov * -viewPos.z * pixelScale / max(NdotL, 0.01));

	vec3 end = start + viewRayDir;

	start = vec3(vec2(gbufferProjection0.x, gbufferProjection0.y) * start.xy, -start.z);
	end   = vec3(vec2(gbufferProjection0.x, gbufferProjection0.y) * end.xy,   -end.z);

	vec3 screenRayDir = (end - start);
	screenRayDir.xy *= 0.5;

	start.xy += gbufferProjection1.xy;
	start.xy *= 0.5;

	#if defined TAA || defined SUPER_RESOLUTION
		float noise = BlueNoiseTemporal().x;
		vec2 offsetCoord = 0.5 + UNIFORM_TAA_JITTER * 0.5;
	#else
		float noise = BlueNoise().x;
		vec2 offsetCoord = vec2(0.5);
	#endif

	float minDist = LinearDepth_From_ScreenDepth(0.7);

	float shadow = 1.0;
	float stepLength = 1.0;

	for (int i = 0; i < 8; i++, start += screenRayDir * stepLength, stepLength += 0.3){
		vec3 samplePos = start + screenRayDir * noise * stepLength;

		if (samplePos.z < minDist) break;

		samplePos.xy = samplePos.xy / samplePos.z + offsetCoord;
		
		if (saturate(samplePos.xy) != samplePos.xy) break;

		#if defined SUPER_RESOLUTION || defined CUSTOM_RENDER_RESOLUTION
			float sampleDepth = textureLod(depthtexS, samplePos.xy * fsrRenderScale, 0.0).x;
		#else
			float sampleDepth = textureLod(depthtexS, samplePos.xy, 0.0).x;
		#endif

		if (sampleDepth < 0.7) break;

		float sampleDist = LinearDepth_From_ScreenDepth(sampleDepth);

		if (samplePos.z - sampleDist > 0.0){
			shadow = 0.0;
			break;
		}
	}

	return shadow * 0.85 + 0.15;
}

void HeldLighting(inout vec3 color, inout vec3 textureLighting, vec3 viewPos, vec3 viewDir, vec3 normal, float roughness, bool isHand){
	float heldLightFalloff = 0.0;

	#if HELDLIGHT_MODE > 0
		
		if (isHand){
			heldLightFalloff = 0.01 * max(heldBlockLightValue, heldBlockLightValue2);
		}else{
			#ifdef FLASHLIGHT_CAMERA_SMOOTH
				float angleRx = eyeRxSmooth * -FLASHLIGHT_CAMERA_SMOOTH_TIME;
				mat3 Rx = mat3(1.0,  0.0,          0.0,
							   0.0,  cos(angleRx), sin(angleRx),
							   0.0, -sin(angleRx), cos(angleRx));

				float angleRy = eyeRySmooth * -FLASHLIGHT_CAMERA_SMOOTH_TIME;
				mat3 Ry = mat3(cos(angleRy), 0.0, -sin(angleRy),
							   0.0,          1.0,  0.0,
							   sin(angleRy), 0.0,  cos(angleRy));

				vec3 aimDir = Ry * Rx * vec3(0.0, 0.0, -1.0);
			#else
				vec3 aimDir = vec3(0.0, 0.0, -1.0);
			#endif

			if (heldBlockLightValue > 0.0){
				vec3 spotPosR = viewPos - vec3(FLASHLIGHT_POS_R_X, FLASHLIGHT_POS_R_Y, 0.0);
				float spotDistR = length(spotPosR);
				vec3 shadowDirR = spotPosR / spotDistR;

				float spotR = pow(max(spotDistR, 0.6), -FLASHLIGHT_FALLOFF);
				spotR *= spotShape(dot(shadowDirR, aimDir));

				#ifdef HELDLIGHT_SHADOW
					spotR *= TorchScreenSpaceShadow(viewPos, viewDir, normal, -shadowDirR);
				#endif
				spotR *= Fd_Burley(normal, -viewDir, -shadowDirR, roughness) * 1.7 + 0.05;
				
				heldLightFalloff += heldBlockLightValue * spotR;
			}

			if (heldBlockLightValue2 > 0.0){
				vec3 spotPosL = viewPos - vec3(FLASHLIGHT_POS_L_X, FLASHLIGHT_POS_L_Y, 0.0);
				float spotDistL = length(spotPosL);
				vec3 shadowDirL = spotPosL / spotDistL;

				float spotL = pow(max(spotDistL, 0.6), -FLASHLIGHT_FALLOFF);
				spotL *= spotShape(dot(shadowDirL, aimDir));

				#ifdef HELDLIGHT_SHADOW
					spotL *= TorchScreenSpaceShadow(viewPos, viewDir, normal, -shadowDirL);
				#endif
				spotL *= Fd_Burley(normal, -viewDir, -shadowDirL, roughness) * 1.7 + 0.05;

				heldLightFalloff += heldBlockLightValue2 * spotL;
			}
		}

		if (isHand){
			textureLighting += vec3(FLASHLIGHT_COLOR_R, FLASHLIGHT_COLOR_G, FLASHLIGHT_COLOR_B) * (heldLightFalloff * (HELDLIGHT_BRIGHTNESS * 0.02));
		}else{
			color += vec3(FLASHLIGHT_COLOR_R, FLASHLIGHT_COLOR_G, FLASHLIGHT_COLOR_B) * (heldLightFalloff * (HELDLIGHT_BRIGHTNESS * 0.02));
		}
		
	#else

		const float temperature = HELDLIGHT_COLOR_TEMPERATURE;
		const mat2x4 splineX = mat2x4(-0.2661293e9, -0.2343589e6, 0.8776956e3, 0.179910,
									-3.0258469e9,  2.1070479e6, 0.2226347e3, 0.240390);

		const mat3x4 splineY = mat3x4(-1.1063814, -1.34811020, 2.18555832, -0.20219683,
									-0.9549476, -1.37418593, 2.09137015, -0.16748867,
									3.0817580, -5.87338670, 3.75112997, -0.37001483);

		const float rt = 1.0 / temperature;
		const float rt2 = rt * rt;
		const vec4 coeffX = vec4(rt2 * rt, rt2, rt, 1.0);
		
		const float xSp0 = dot(coeffX, splineX[0]) + saturate((temperature - 4000.0) * 1e10) * 1e10;
		const float xSp1 = dot(coeffX, splineX[0]) + saturate((3999.9 - temperature) * 1e10) * 1e10;

		const float x = min(xSp0, xSp1);
		const float x2 = x * x;
		const vec4 coeffY = vec4(x2 * x, x2, x, 1.0);

		const float zSp0 = dot(coeffY, splineY[0]) + saturate((0.504166 - x) * 1e10) * 1e10;
		const float zSp1 = dot(coeffY, splineY[1]) + saturate((x - 0.504167) * 1e10) * 1e10 + saturate((0.380007 - x) * 1e10) * 1e10;
		const float zSp2 = dot(coeffY, splineY[2]) + saturate((x - 0.380008) * 1e10) * 1e10;

		const float z = 1.0 / min3(zSp0, zSp1, zSp2);

		const float xyzX = x * z;
		const float xyzZ = z - xyzX - 1.0;


		const mat3 xyzToSrgb = mat3( 3.24097, -0.96924,  0.05563,
									-1.53738,  1.87597, -0.20398,
									-0.49861,  0.04156,  1.05697);

		const vec3 blackbody = max(xyzToSrgb * vec3(xyzX, 1.0, xyzZ), vec3(0.0));

		if (isHand){
			//#if TEXTURE_EMISSIVENESS_MODE != 1
				#if HELDLIGHT_FALLODD_MODE > 0
					heldLightFalloff = 0.5 * max(heldBlockLightValue, heldBlockLightValue2);
				#else
					heldLightFalloff = 0.3 * max(heldBlockLightValue, heldBlockLightValue2);
				#endif
			//#endif
		}else{
			#ifdef HELDLIGHT_SHADOW

				if (heldBlockLightValue > 0.0){
					vec3 torchPosR = viewPos - vec3(FLASHLIGHT_POS_R_X, FLASHLIGHT_POS_R_Y, 0.0);
					float torchDistR = length(torchPosR);

					#if HELDLIGHT_FALLODD_MODE > 0
						float torchR = saturate(1.0 - torchDistR * 0.065);
						torchDistR = torchDistR + 1.1;

						#if TORCHLIGHT_FALLOFF_MODE > 0
							float boost = saturate(torchR * 2.0 - 1.0);
							torchR = torchR * 0.5 + boost * boost * 0.5;
						#endif
						
						torchR *= 1.0 / (torchDistR * torchDistR);
					#else
						torchDistR += 4.0 / (torchDistR + 2.0);
						float torchR = pow(torchDistR, -HELDLIGHT_FALLOFF);
					#endif

					vec3 shadowDirR = -normalize(viewPos - vec3(FLASHLIGHT_POS_R_X, FLASHLIGHT_POS_R_Y, 0.0));
					torchR *= TorchScreenSpaceShadow(viewPos, viewDir, normal, shadowDirR);
					torchR *= Fd_Burley(normal, -viewDir, shadowDirR, roughness) * 1.7 + 0.05;

					heldLightFalloff += heldBlockLightValue * torchR;
				}

				if (heldBlockLightValue2 > 0.0){
					vec3 torchPosL = viewPos - vec3(FLASHLIGHT_POS_L_X, FLASHLIGHT_POS_L_Y, 0.0);
					float torchDistL = length(torchPosL);

					#if HELDLIGHT_FALLODD_MODE > 0
						float torchL = saturate(1.0 - torchDistL * 0.065);
						torchDistL = torchDistL + 1.1;

						#if TORCHLIGHT_FALLOFF_MODE > 0
							float boost = saturate(torchL * 2.0 - 1.0);
							torchL = torchL * 0.5 + boost * boost * 0.5;
						#endif
						
						torchL *= 1.0 / (torchDistL * torchDistL);
					#else
						torchDistL += 4.0 / (torchDistL + 2.0);
						float torchL = pow(torchDistL, -HELDLIGHT_FALLOFF);
					#endif

					vec3 shadowDirL = -normalize(viewPos - vec3(FLASHLIGHT_POS_L_X, FLASHLIGHT_POS_L_Y, 0.0));
					torchL *= TorchScreenSpaceShadow(viewPos, viewDir, normal, shadowDirL);
					torchL *= Fd_Burley(normal, -viewDir, shadowDirL, roughness) * 1.7 + 0.05;

					heldLightFalloff += heldBlockLightValue2 * torchL;
				}

			#else

				float torchDist = length(viewPos);
				torchDist += 4.0 / (torchDist + 2.0);
				heldLightFalloff = pow(torchDist, -HELDLIGHT_FALLOFF);
				heldLightFalloff *= Fd_Burley(normal, -viewDir, -viewDir, roughness) + 0.3;

				heldLightFalloff *= heldBlockLightValue + heldBlockLightValue2;

			#endif
		}

		#if HELDLIGHT_FALLODD_MODE > 0
			heldLightFalloff *= HELDLIGHT_BRIGHTNESS * BLOCKLIGHT_BRIGHTNESS * 0.02;
		#else
			heldLightFalloff *= HELDLIGHT_BRIGHTNESS * BLOCKLIGHT_BRIGHTNESS * 0.01;
		#endif

		if (isHand){
			textureLighting += blackbody * heldLightFalloff;
		}else{
			color += blackbody * heldLightFalloff;
		}
		
	#endif

}


vec3 TorchSpecularHighlight(vec3 viewPos, vec3 viewDir, float dist, vec3 normal, float roughness, vec3 f0){
	roughness = max(roughness, 0.002);

	#if HELDLIGHT_MODE > 0

		#ifdef FLASHLIGHT_CAMERA_SMOOTH
			float angleRx = eyeRxSmooth * -FLASHLIGHT_CAMERA_SMOOTH_TIME;
			mat3 Rx = mat3(1.0,  0.0,          0.0,
							0.0,  cos(angleRx), sin(angleRx),
							0.0, -sin(angleRx), cos(angleRx));

			float angleRy = eyeRySmooth * -FLASHLIGHT_CAMERA_SMOOTH_TIME;
			mat3 Ry = mat3(cos(angleRy), 0.0, -sin(angleRy),
							0.0,          1.0,  0.0,
							sin(angleRy), 0.0,  cos(angleRy));

			vec3 aimDir = Ry * Rx * vec3(0.0, 0.0, -1.0);
		#else
			vec3 aimDir = vec3(0.0, 0.0, -1.0);
		#endif

		vec3 heldHighlight = vec3(0.0);
		
		if (heldBlockLightValue > 0.0){
			vec3 spotPosR = viewPos - vec3(FLASHLIGHT_POS_R_X, FLASHLIGHT_POS_R_Y, 0.0);
			float spotDistR = length(spotPosR);
			vec3 shadowDirR = spotPosR / spotDistR;

			float spotR = pow(max(spotDistR, 0.6), -FLASHLIGHT_FALLOFF);
			spotR *= spotShape(dot(shadowDirR, aimDir));
			
			heldHighlight += SpecularGGX(normal, -viewDir, -shadowDirR, roughness, f0) * (heldBlockLightValue * spotR);
		}

		if (heldBlockLightValue2 > 0.0){
			vec3 spotPosL = viewPos - vec3(FLASHLIGHT_POS_L_X, FLASHLIGHT_POS_L_Y, 0.0);
			float spotDistL = length(spotPosL);
			vec3 shadowDirL = spotPosL / spotDistL;

			float spotL = pow(max(spotDistL, 0.6), -FLASHLIGHT_FALLOFF);
			spotL *= spotShape(dot(shadowDirL, aimDir));

			heldHighlight += SpecularGGX(normal, -viewDir, -shadowDirL, roughness, f0) * (heldBlockLightValue2 * spotL);
		}

		return (vec3(FLASHLIGHT_COLOR_R, FLASHLIGHT_COLOR_G, FLASHLIGHT_COLOR_B) * HELDLIGHT_BRIGHTNESS * 0.002) * heldHighlight;

	#else

		const float temperature = HELDLIGHT_COLOR_TEMPERATURE;
		const mat2x4 splineX = mat2x4(-0.2661293e9, -0.2343589e6, 0.8776956e3, 0.179910,
									-3.0258469e9,  2.1070479e6, 0.2226347e3, 0.240390);

		const mat3x4 splineY = mat3x4(-1.1063814, -1.34811020, 2.18555832, -0.20219683,
									-0.9549476, -1.37418593, 2.09137015, -0.16748867,
									3.0817580, -5.87338670, 3.75112997, -0.37001483);

		const float rt = 1.0 / temperature;
		const float rt2 = rt * rt;
		const vec4 coeffX = vec4(rt2 * rt, rt2, rt, 1.0);
		
		const float xSp0 = dot(coeffX, splineX[0]) + saturate((temperature - 4000.0) * 1e10) * 1e10;
		const float xSp1 = dot(coeffX, splineX[0]) + saturate((3999.9 - temperature) * 1e10) * 1e10;

		const float x = min(xSp0, xSp1);
		const float x2 = x * x;
		const vec4 coeffY = vec4(x2 * x, x2, x, 1.0);

		const float zSp0 = dot(coeffY, splineY[0]) + saturate((0.504166 - x) * 1e10) * 1e10;
		const float zSp1 = dot(coeffY, splineY[1]) + saturate((x - 0.504167) * 1e10) * 1e10 + saturate((0.380007 - x) * 1e10) * 1e10;
		const float zSp2 = dot(coeffY, splineY[2]) + saturate((x - 0.380008) * 1e10) * 1e10;

		const float z = 1.0 / min3(zSp0, zSp1, zSp2);

		const float xyzX = x * z;
		const float xyzZ = z - xyzX - 1.0;


		const mat3 xyzToSrgb = mat3( 3.24097, -0.96924,  0.05563,
									-1.53738,  1.87597, -0.20398,
									-0.49861,  0.04156,  1.05697);

		const vec3 blackbody = max(xyzToSrgb * vec3(xyzX, 1.0, xyzZ), vec3(0.0));

		vec3 heldHighlight = SpecularGGX(normal, -viewDir, -viewDir, roughness, f0);

		if (any(greaterThan(heldHighlight, vec3(0.001)))){
			#if HELDLIGHT_FALLODD_MODE > 0
				float torch = saturate(1.0 - dist * 0.065);
				
				#if TORCHLIGHT_FALLOFF_MODE > 0
					float boost = saturate(torch * 2.0 - 1.0);
					torch = torch * 0.5 + boost * boost * 0.5;
				#endif

				float falloffDist = dist + 1.1;			
				float heldLightFalloff = torch / (falloffDist * falloffDist);
			#else
				dist += 4.0 / (dist + 2.0);
				float heldLightFalloff = pow(dist, -HELDLIGHT_FALLOFF);
			#endif

			heldLightFalloff *= (heldBlockLightValue + heldBlockLightValue2);

			#if HELDLIGHT_FALLODD_MODE > 0
				heldLightFalloff *= HELDLIGHT_BRIGHTNESS * BLOCKLIGHT_BRIGHTNESS * 0.002;
			#else
				heldLightFalloff *= HELDLIGHT_BRIGHTNESS * BLOCKLIGHT_BRIGHTNESS * 0.001;
			#endif

			return blackbody * heldHighlight * heldLightFalloff;
		}
	#endif
}
