// Parameters:
/// OBS Default
uniform float4x4 ViewProj;
/// Texture
uniform texture2d pImage;
uniform float2 pImageTexel;
/// Blur
uniform float pSize;
uniform float pAngle; 
uniform float2 pCenter;
uniform float2 pStepScale;
/// Gaussian
uniform float4 pKernel[32];

#define MAX_BLUR_SIZE 128

// Sampler
sampler_state linearSampler {
	Filter    = Linear;
	AddressU  = Clamp;
	AddressV  = Clamp;
	MinLOD    = 0;
	MaxLOD    = 0;
};

// Default Vertex Shader and Data
struct VertDataIn {
	float4 pos : POSITION;
	float2 uv  : TEXCOORD0;
};

struct VertDataOut {
	float4 pos : POSITION;
	float2 uv  : TEXCOORD0;
};

VertDataOut VSDefault(VertDataIn vtx) {
	VertDataOut vert_out;
	vert_out.pos = mul(float4(vtx.pos.xyz, 1.0), ViewProj);
	vert_out.uv  = vtx.uv;
	return vert_out;
}

// Functions
float GetKernelAt(int i) {
	return ((float[4])(pKernel[floor(i/4)]))[i%4];
}

// Blur 1 Dimensional
float4 PSBlur1D(VertDataOut vtx) : TARGET {
	float4 final = pImage.Sample(linearSampler, vtx.uv)
		* GetKernelAt(0);

	// Loop unrolling is only possible with a fixed known maximum.
	// Some compilers may unroll up to x iterations, but most will not.
	for (int n = 1; n <= MAX_BLUR_SIZE; n++) {
		float2 nstep = (pImageTexel * pStepScale) * n;
		float kernel = GetKernelAt(n);
		final += pImage.Sample(linearSampler, vtx.uv + nstep) * kernel;
		final += pImage.Sample(linearSampler, vtx.uv - nstep) * kernel;

		if (n >= pSize) {
			break;
		}
	}

	return final;
}

technique Draw {
	pass {
		vertex_shader = VSDefault(vtx);
		pixel_shader  = PSBlur1D(vtx);
	}
}

// Blur Rotation
float2 rotate(float2 pt, float angle) {
	float cp = cos(angle);
	float sp = sin(angle);
	float sn = -sp;
	return float2((pt.x * cp) + (pt.y * sn), (pt.x * sp) + (pt.y * cp));
}

float2 rotateAround(float2 pt, float2 cpt, float angle) {
	return rotate(pt - cpt, angle) + cpt;
}

float4 PSRotate(VertDataOut vtx) : TARGET {
	float4 final = pImage.Sample(linearSampler, vtx.uv)
		* GetKernelAt(0);
	
	float angstep = pAngle * pStepScale.x;

	// Loop unrolling is only possible with a fixed known maximum.
	// Some compilers may unroll up to x iterations, but most will not.
	for (int n = 1; n <= MAX_BLUR_SIZE; n++) {
		float kernel = GetKernelAt(n);
		final += pImage.Sample(linearSampler, rotateAround(vtx.uv, pCenter, angstep * n)) * kernel;
		final += pImage.Sample(linearSampler, rotateAround(vtx.uv, pCenter, angstep * -n)) * kernel;

		if (n >= pSize) {
			break;
		}
	}
	
	return final;
}

technique Rotate {
	pass {
		vertex_shader = VSDefault(vtx);
		pixel_shader  = PSRotate(vtx);
	}
}

// Blur Zoom
float4 PSZoom(VertDataOut vtx) : TARGET {
	float4 final = pImage.Sample(linearSampler, vtx.uv)
		* GetKernelAt(0);

	// step is calculated from the direction relative to the center
	float2 dir = normalize(vtx.uv - pCenter) * pStepScale * pImageTexel;
	float dist = distance(vtx.uv, pCenter);

	// Loop unrolling is only possible with a fixed known maximum.
	// Some compilers may unroll up to x iterations, but most will not.
	for (int n = 1; n <= MAX_BLUR_SIZE; n++) {
		float kernel = GetKernelAt(n);
		final += pImage.Sample(linearSampler, vtx.uv + (dir * n) * dist) * kernel;
		final += pImage.Sample(linearSampler, vtx.uv - (dir * n) * dist) * kernel;

		if (n >= pSize) {
			break;
		}
	}

	return final;
}

technique Zoom {
	pass {
		vertex_shader = VSDefault(vtx);
		pixel_shader  = PSZoom(vtx);
	}
}
