
// requires: positionFromDepth(vFrustum, depth_pos);
// requires: calculate_shadow_for_position(ray_world.xyz, depth_pos, cascadeColor);


float vol_rand(float n){return fract(sin(n) * 43758.5453123);}

float vol_rand(vec2 n) { 
	return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}

float vol_noise(vec2 n) {
	const vec2 d = vec2(0.0, 1.0);
	vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
	return mix(mix(vol_rand(b), vol_rand(b + d.yx), f.x), mix(vol_rand(b + d.xy), vol_rand(b + d.yy), f.x), f.y);
}

float volumetric_sample_shadow(in LightProperties light, in vec4 frustum, in float max_depth, out vec4 cascade_color)
{
	float fact = 0.0;
	float depth_dir = 0.1;	// this is completely arbitrary...
	float depth_pos = 0.0;

	vec4 projector_color = vec4(0.0);

	// jitter for banding removal. this is currently also completely arbitrary....
	depth_pos = depth_pos + depth_dir * vol_noise(gl_FragCoord.xy) * 0.5;

	int i;
	for(i=0; i<256;i++)
	{
		vec3 ray_world = positionFromDepth(frustum, depth_pos);
		float lf = calculate_shadow_for_position(light, ray_world.xyz, depth_pos, cascade_color, projector_color);

		fact += lf;

		depth_dir = depth_dir * 1.01;
		depth_pos += depth_dir;

		if (depth_pos > max_depth)
			break;
	}

	return fact / float(i + 1) *  1.0;
}

float volumetric_sample_shadow_spot(in LightProperties light, in vec4 frustum, in float max_depth, out float attenuation, out vec4 projector_color)
{
	float fact = 0.0;
	float depth_dir = 0.1;	// this is completely arbitrary...
	float depth_pos = 0.0;

	vec4 cascade_color;

	attenuation = 0.0;

	// jitter for banding removal. this is currently also completely arbitrary....
	depth_pos = depth_pos + depth_dir * vol_noise(gl_FragCoord.xy + g_global_time) * 2.5;

	int i;

	for(i=0; i<256;i++)
	{
		vec3 ray_world = positionFromDepth(frustum, depth_pos);
		float lf = 0.0;

		float falloff = dot(light.direction.xyz, normalize(ray_world - light.position.xyz));

		if (falloff > light.cutoff)
		{
			float sample_attenuation = 1.0 - (1.0 - falloff) / (1.0 - light.cutoff);
			sample_attenuation *= 1.0 - clamp(length(light.position.xyz - ray_world.xyz) / light.range, 0.0, 1.0);
			sample_attenuation = pow(sample_attenuation, 3.0);

			vec4 projector;
			float shadow_dist;
			float shadow_value;
			shadow_value = calculate_shadow_dist_for_position(light, ray_world.xyz, depth_pos, cascade_color, projector, shadow_dist);
			lf = sample_attenuation * shadow_value;
			attenuation += sample_attenuation;
			
			// maybe just consider using the ground level??
			#if 0
			if (dot(projector.rgb, projector.rgb) > 0.0 && shadow_dist > 0.0)
			{
				// distance to the projection plane
				//float threshold = 0.0028;
				const float threshold = 0.0038;
				const float min_shadow_dist_factor = 0.001;
				if (shadow_dist < threshold)
				{
					float noise = snoise(ray_world.xyz * 2.0 + vec3(g_global_time, 0.0, -gTime * 0.3));
					noise += snoise(ray_world.xyz * 1.10231 + vec3(-g_global_time * 0.21, 3.021, -gTime * 0.031));
					//float noise = 1.0;

					float f = (threshold - shadow_dist) * (1.0 / threshold);
					f = f * f;	// not really needed? -- kiero
					shadow_dist = f * (0.5 + 0.5 * noise);
				}
				else
					shadow_dist = 0.0;

				projector_color.rgb += sample_attenuation * projector.rgb * (min_shadow_dist_factor + (1.0 - min_shadow_dist_factor) * shadow_dist);
			}
			#else
			projector_color.rgb += projector.rgb * (1.0 - shadow_value) * sample_attenuation;
			#endif
		}

		fact += lf;

		depth_dir = depth_dir * 1.01;
		depth_pos += depth_dir;

		if (depth_pos > max_depth)
			break;
	}

	projector_color.rgb /= (float(i + 1));
	return (attenuation - fact) / (float(i + 1));
}