/*
	Bilateral v1.0
	by Sp00kyFox, 2014

Bilateral Filter, calculates a weighted mean of surrounding pixels based on color and spatial distance.
This can be used to smooth color transitions or blend dithering to some extent while preserving sharp edges.
Increasing the radius leads to more pixel lookups and therefore to a lower shader performance.

*/

#include "../compat_includes.inc"
uniform COMPAT_Texture2D(decal) : TEXUNIT0;
uniform float4x4 modelViewProj;

#pragma parameter RAD   "BILATERAL Radius"        2.00 0.00 12.0 0.25
#pragma parameter CLR   "BILATERAL Color Thresh"  0.15 0.01  1.0 0.01
#pragma parameter CWGHT "BILATERAL Central Wght"  0.25 0.00  2.0 0.05

#ifdef PARAMETER_UNIFORM
	uniform float RAD, CLR, CWGHT;
#else
	#define RAD   2.00
	#define CLR   0.15
	#define CWGHT 0.25
#endif


#define TEX(dx,dy) COMPAT_SamplePoint(decal, texCoord+float2((dx),(dy))*t1)

static float4 unit4  = float4(1.0, 1.0, 1.0, 1.0);

static int   steps = ceil(RAD);
static float clr   = -CLR * CLR;
static float sigma = RAD * RAD / 2.0;
static float cwght = 1.0 + CWGHT * max(1.0, 2.87029746*sigma + 0.43165242*RAD - 0.25219746);

static float domain[13] = {1.0, exp( -1.0/sigma), exp( -4.0/sigma), exp( -9.0/sigma), exp( -16.0/sigma), exp( -25.0/sigma), exp( -36.0/sigma),
				exp(-49.0/sigma), exp(-64.0/sigma), exp(-81.0/sigma), exp(-100.0/sigma), exp(-121.0/sigma), exp(-144.0/sigma)};

float dist2(float3 pt1, float3 pt2)
{
	float3 v = pt1 - pt2;
	return dot(v,v);
}

float4 weight(int i, int j, float3 org, float4x3 A)
{
	return domain[i] * domain[j] * exp(float4(dist2(org,A[0]), dist2(org,A[1]), dist2(org,A[2]), dist2(org,A[3]))/clr);
}

struct out_vertex {
	float4 position : POSITION;
	float2 texCoord : TEXCOORD0;
	float2 t1;
#ifndef HLSL_4
	float4 Color    : COLOR;
#endif
};

/*    VERTEX_SHADER    */
out_vertex main_vertex(COMPAT_IN_VERTEX)
{
	out_vertex OUT;
#ifdef HLSL_4
	float4 position = VIN.position;
	float2 texCoord = VIN.texCoord;
#else
	OUT.Color = color;
#endif

	OUT.position = mul(modelViewProj, position);

	OUT.texCoord = texCoord;
	OUT.t1       = 1.0/COMPAT_texture_size;

	return OUT;
}


/*    FRAGMENT SHADER    */
float4 bilateral(float2 texture_size, float2 video_size, float2 output_size, COMPAT_Texture2D(decal), float2 texCoord, float2 t1)
{
	float4x3 A, B;
	float4 wghtA, wghtB;
	float3 org = TEX(0,0).rgb, result = cwght*org;
	float  norm = cwght;
	

	for(int x=1; x<=steps; x++){
		
		A = float4x3(TEX( x, 0).rgb, TEX(-x, 0).rgb, TEX( 0, x).rgb, TEX( 0,-x).rgb);
		B = float4x3(TEX( x, x).rgb, TEX( x,-x).rgb, TEX(-x, x).rgb, TEX(-x,-x).rgb);

		wghtA = weight(x, 0, org, A); wghtB = weight(x, x, org, B);	

		result += mul(wghtA, A)     + mul(wghtB, B);
		norm   += dot(wghtA, unit4) + dot(wghtB, unit4);
		
		for(int y=1; y<x; y++){
					
			A = float4x3(TEX( x, y).rgb, TEX( x,-y).rgb, TEX(-x, y).rgb, TEX(-x,-y).rgb);
			B = float4x3(TEX( y, x).rgb, TEX( y,-x).rgb, TEX(-y, x).rgb, TEX(-y,-x).rgb);

			wghtA = weight(x, y, org, A); wghtB = weight(y, x, org, B);	

			result += mul(wghtA, A)     + mul(wghtB, B);
			norm   += dot(wghtA, unit4) + dot(wghtB, unit4);
		}
	}

	return float4(result/norm, 1.0);
}

float4 main_fragment(COMPAT_IN_FRAGMENT) : COMPAT_Output
{
	return bilateral(COMPAT_texture_size, COMPAT_video_size, COMPAT_output_size, decal, VOUT.texCoord, VOUT.t1);
}
COMPAT_END