///////////////////////////////////////////////////////////////////////////////
// Blur filter setting
//
// min: blurFalloff 8, sharpness 10/8
// mid: blurFalloff 6, sharpness 8
// max: blurFalloff 4, sharpness 6
///////////////////////////////////////////////////////////////////////////////
#define blurFalloff 4.0
#define sharpness   6.0

///////////////////////////////////////////////////////////////////////////////
// Sharpen filter setting
//
// min: Sharpen 0.5,  EdgeSharpen  1.0
// mid: Sharpen 1.0,  EdgeSharpen -0.25
// max: Sharpen 1.75, EdgeSharpen -0.5  (1.5, -0.25)
///////////////////////////////////////////////////////////////////////////////
#define Sharpen      0.5
#define EdgeSharpen  1.0


texture bloomMap;
sampler s0;

sampler BloomSampler : samplerstate
{
	Texture = bloomMap;
	MinFilter = Linear;
	MagFilter = Linear;
	AddressU = Clamp;
	AddressV = Clamp;
};

// Variables that set the width and height of the current game resolution from sfall (only for global shaders)
int w;
int h;

// the pixel size is computed as: 1.0f / resolution
float2 rcpres;

// system ticks (received from sfall)
//int tickcount;

static const float2 wPass = float2(1, 0);
static const float2 hPass = float2(0, 1);

float Brightness(float3 color)
{
	return 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
}

float3 BlurFunction(float2 uv, float brightness, inout float totalWeight)
{
	float3 pointColor = (tex2Dlod(s0, float4(uv, 0, 0)).rgb);

	float diff = abs(brightness - Brightness(pointColor));
	float weight = exp2((-0.25 * blurFalloff) - (diff * sharpness));
	totalWeight += weight;

	return pointColor * weight;
}

float4 BlurDeltaPassPS(float2 uv : TEXCOORD0, uniform float2 delta) : COLOR0
{
	float3 sumColor = (tex2D(s0, uv).rgb);
	float brightness = Brightness(sumColor);

	delta *= rcpres;
	float totalWeight = 1;

	float2 pointUV = uv + delta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	pointUV = uv - delta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	return float4((sumColor / totalWeight), 1);
}

static const float2 wDelta = float2(1, 0) * rcpres;
static const float2 hDelta = float2(0, 1) * rcpres;

float4 BlurPS(float4 color : COLOR0, float2 uv : TEXCOORD0) : COLOR0
{
	float totalWeight = 1;

	float3 sumColor = (tex2D(s0, uv).rgb);
	float brightness = Brightness(sumColor);

	float2 pointUV = uv + wDelta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	pointUV = uv - wDelta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	pointUV = uv + hDelta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	pointUV = uv - hDelta;
	sumColor += BlurFunction(pointUV, brightness, totalWeight);

	return float4((sumColor / totalWeight), 1);
}

///////////////////////////////////////////////////////////////////////////////
// Bloom effect setting
//
// min: BloomIntensity .8
// mid: BloomIntensity .85
// max: BloomIntensity .9
///////////////////////////////////////////////////////////////////////////////
#define BaseIntensity   1.2
#define BaseSaturation  1.0

#define BloomIntensity  0.8
#define BloomSaturation 0.35

#define BloomThreshold  0.35
#define BlurPower       0.002

// pseudo gauss blur
static const float2 offsets[12] = {
	-0.326212, -0.405805,
	-0.840144, -0.073580,
	-0.695914,  0.457137,
	-0.203345,  0.620716,
	 0.962340, -0.194983,
	 0.473434, -0.480026,
	 0.519456,  0.767022,
	 0.185461, -0.893124,
	 0.507431,  0.064425,
	 0.896420,  0.412458,
	-0.321940, -0.932615,
	-0.791559, -0.597705,
};

float4 AdjustSaturation(float4 color, float saturation)
{
	float grey = dot(color, float3(0.3, 0.59, 0.11));
	return lerp(grey, color, saturation);
}

float4 BloomPS(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
	float4 original = tex2D(s0, texCoord);
	original = AdjustSaturation(original, BaseSaturation) * BaseIntensity;
	float sum = (original.r + original.g + original.b) * 0.333;
	if (sum >= 0.95) return original;

	// bloom blur
	float4 bloomBlur = tex2D(s0, texCoord);
	for(int i = 0; i < 12; i++)
	{
		bloomBlur += tex2D(s0, texCoord + BlurPower * offsets[i]);
	}
	bloomBlur *= 0.085;

	float intensity = BloomIntensity - lerp(0.0, 0.2, sum);
	bloomBlur = AdjustSaturation(bloomBlur, BloomSaturation) * intensity;
	bloomBlur = saturate((bloomBlur - BloomThreshold) / (1 - BloomThreshold));

	return bloomBlur + original;
}

static const float CoefBlur = (Sharpen * 0.5);
static const float CoefOrig = (1 + CoefBlur);

// for the sharpen edge
#define SharpenEdge 0.4
static const float sharpenVal0 = (Sharpen * (1.5 + EdgeSharpen));
static const float sharpenVal1 = ((sharpenVal0 - 1) / 8.0);

// pixel "width"
static const float px = rcpres.x;
static const float py = rcpres.y;

// for the blur filter
#define mean 1.0
static const float dx = (mean * px);
static const float dy = (mean * py);

float4 SharpenComplexPS(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
	float4 c0 = tex2D(s0, texCoord); // get original pixel

	// edge detection
	// Get neighbor points
	// [ c1, c2, c3 ]
	// [ c4, c0, c5 ]
	// [ c6, c7, c8 ]
	float4 c1 = tex2D(s0, texCoord + float2(-px, -py));
	float4 c2 = tex2D(s0, texCoord + float2(  0, -py));
	float4 c3 = tex2D(s0, texCoord + float2( px, -py));
	float4 c4 = tex2D(s0, texCoord + float2(-px,   0));
	float4 c5 = tex2D(s0, texCoord + float2( px,   0));
	float4 c6 = tex2D(s0, texCoord + float2(-px,  py));
	float4 c7 = tex2D(s0, texCoord + float2(  0,  py));
	float4 c8 = tex2D(s0, texCoord + float2( px,  py));

	// using Sobel filter
	// horizontal gradient
	// [ -1, 0, 1 ]
	// [ -2, 0, 2 ]
	// [ -1, 0, 1 ]
	float delta1 = (c3 + 2 * c5 + c8) - (c1 + 2 * c4 + c6);

	// vertical gradient
	// [ -1, - 2, -1 ]
	// [  0,   0,  0 ]
	// [  1,   2,  1 ]
	float4 c13 = c1 + c3;
	float4 c68 = c6 + c8;
	float delta2 = (c68 + 2 * c7) - (c13 + 2 * c2);

	// computation
	if (sqrt(mul(delta1, delta1) + mul(delta2, delta2)) > SharpenEdge) {
		// if we have an edge, use sharpen
		return c0 * sharpenVal0 - (c13 + c2 + c4 + c5 + c68 + c7) * sharpenVal1;

		// Display edges in red...
		//return float4( 1.0, 0.0, 0.0, 0.0 );
	} else {
		// else return corrected image

		// compute blurred image (gaussian filter)
		c1 = tex2D(s0, texCoord + float2(-dx, -dy));
		c2 = tex2D(s0, texCoord + float2(  0, -dy));
		c3 = tex2D(s0, texCoord + float2( dx, -dy));
		c4 = tex2D(s0, texCoord + float2(-dx,   0));
		c5 = tex2D(s0, texCoord + float2( dx,   0));
		c6 = tex2D(s0, texCoord + float2(-dx,  dy));
		c7 = tex2D(s0, texCoord + float2(  0,  dy));
		c8 = tex2D(s0, texCoord + float2( dx,  dy));

		// gaussian filter
		// [ 1, 2, 1 ]
		// [ 2, 4, 2 ]
		// [ 1, 2, 1 ]
		// to normalize the values, we need to divide by the coeff sum
		float4 flou = (c1 + c3 + c6 + c8 + 2 * (c2 + c4 + c5 + c7) + 4 * c0) * 0.0625; // 1 / (1+2+1+2+4+2+1+2+1) = 1 / 16 = 0.0625

		// substract blurred image from original image
		return (CoefOrig * c0 - CoefBlur * flou);
	}
}

technique BlurBloom
{
	//pass P0 { PixelShader = compile ps_3_0 BlurDeltaPassPS(wPass); }
	//pass P1 { PixelShader = compile ps_3_0 BlurDeltaPassPS(hPass); }

	pass P0 { PixelShader = compile ps_3_0 BlurPS(); }
	pass P3 { PixelShader = compile ps_2_0 BloomPS(); }
	pass P2 { PixelShader = compile ps_2_0 SharpenComplexPS(); }
}
