// -------------------------------------------------------------------------------- //
// Defines
#define MAX_DISTANCE	65536.0
#define NEAR_INFINITE	18446744073709551616.0
#define FLT_SMALL		0.001
#define PI				3.1415926535897932384626433832795
#define HALFPI			1.5707963267948966192313216916398
// -------------------------------------------------------------------------------- //

// -------------------------------------------------------------------------------- //
// Samplers
sampler_state imageSampler {
	Filter		= Linear;
	AddressU	= Clamp;
	AddressV	= Clamp;
};

sampler_state sdfSampler {
	Filter		= Linear;
	AddressU	= Clamp;
	AddressV	= Clamp;
	BorderColor	= FFFFFFFF;
};
// -------------------------------------------------------------------------------- //

// -------------------------------------------------------------------------------- //
// Global Parameters
uniform float4x4 ViewProj;
uniform texture2d pSDFTexture;
uniform float pSDFThreshold;
uniform texture2d pImageTexture;
// -------------------------------------------------------------------------------- //

// -------------------------------------------------------------------------------- //
// Shared Functions
float4 PremultiplyTarget(float4 c, float4 t) {
	return lerp(float4(t.r, t.g, t.b, c.a), c, c.a);
}

float LinearCurveFromValue(float v, float offset, float width,
						   float sharpness, float sharpnessInverse) {
}

float GradientFromValue(float v, float offset, float width) {
	return ((v - offset) / width);
}

// We can use any of the following gradient functions: https://www.desmos.com/calculator/bmbrncaiem
float CurveEaseInOut(float v) {
	return cos((v-1)*PI)*0.5+0.5;
}

float CurveEaseOut(float v) {
	return sin(v * HALFPI);
}

float CurveEaseIn(float v) {
	return 1 - cos(v * HALFPI);
}
// -------------------------------------------------------------------------------- //

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

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

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

// -------------------------------------------------------------------------------- //
// Shadow Effects (Inner, Outer)
uniform float4 pShadowColor;
uniform float pShadowMin;
uniform float pShadowMax;
uniform float2 pShadowOffset;

float4 PSShadowOuter(VertDataOut v_in) : TARGET
{
	float2 dist_ex = pSDFTexture.Sample(sdfSampler, v_in.uv + pShadowOffset).rg * MAX_DISTANCE;
	float dist = dist_ex.r - dist_ex.g;
	bool mask = (pImageTexture.Sample(imageSampler, v_in.uv).a <= pSDFThreshold);

	if (!mask) {
		return float4(0.0, 0.0, 0.0, 0.0);
	}

	float v = clamp((dist - pShadowMin) / (pShadowMax - pShadowMin), 0., 1.);
	return float4(pShadowColor.r, pShadowColor.g, pShadowColor.b, (1.0 - v) * pShadowColor.a);	
}

technique ShadowOuter
{
	pass
	{
		vertex_shader = VSDefault(v_in);
		pixel_shader  = PSShadowOuter(v_in);
	}
}

float4 PSShadowInner(VertDataOut v_in) : TARGET
{
	float2 dist_ex = pSDFTexture.Sample(sdfSampler, v_in.uv + pShadowOffset).rg * MAX_DISTANCE;
	float dist = dist_ex.g - dist_ex.r;
	bool mask = (pImageTexture.Sample(imageSampler, v_in.uv).a > pSDFThreshold);

	if (!mask) {
		return float4(0.0, 0.0, 0.0, 0.0);
	}

	float v = clamp((dist - pShadowMin) / (pShadowMax - pShadowMin), 0., 1.);
	return float4(pShadowColor.r, pShadowColor.g, pShadowColor.b, (1.0 - v) * pShadowColor.a);	
}

technique ShadowInner
{
	pass
	{
		vertex_shader = VSDefault(v_in);
		pixel_shader  = PSShadowInner(v_in);
	}
}
// -------------------------------------------------------------------------------- //

// -------------------------------------------------------------------------------- //
// Glow
uniform float4 pGlowColor <
	string name = "Glow Color";
	string group = "Glow";
>;
uniform float pGlowWidth <
	string name = "Glow Width";
	string group = "Glow";
	float minimum = 0.01;
	float maximum = 16.0;
	float delta = 0.01;
	float default = 1.0;
>;
uniform float pGlowSharpness <
	string name = "Glow Sharpness";
	string group = "Glow";
	float minimum = 0.01;
	float maximum = 100.0;
	float delta = 0.01;
	float default = 1.0;
	string set_expr = "x / 100.0";
>;
uniform float pGlowSharpnessInverse <
	string name = "Glow Sharpness Inverse";
	string group = "Glow";
	string value_expr = "1.0 / (1.0 - pGlowSharpness)";
	bool visible = false;
	bool enabled = false;
>;

float4 GlowShared(float dist) {
	
	// Calculate correct gradient value and also take into account glow alpha to not delete information.
	float v = clamp((GradientFromValue(dist, 0, pGlowWidth) - pGlowSharpness) * pGlowSharpnessInverse, 0.0, 1.0);
	return float4(pGlowColor.r, pGlowColor.g, pGlowColor.b, pGlowColor.a * (1.0 - v));
}

float4 PSGlowOuter(VertDataOut v_in) : TARGET
{
	float dist = pSDFTexture.Sample(sdfSampler, v_in.uv).r * MAX_DISTANCE;
	bool mask  = (pImageTexture.Sample(imageSampler, v_in.uv).a <= pSDFThreshold);

	if (!mask) {
		return float4(0.0, 0.0, 0.0, 0.0);
	}

	return GlowShared(dist);
}

technique GlowOuter
{
	pass
	{
		vertex_shader = VSDefault(v_in);
		pixel_shader = PSGlowOuter(v_in);
	}
}

float4 PSGlowInner(VertDataOut v_in) : TARGET
{
	float dist = pSDFTexture.Sample(sdfSampler, v_in.uv).g * MAX_DISTANCE;
	bool mask  = (pImageTexture.Sample(imageSampler, v_in.uv).a > pSDFThreshold);

	if (!mask) {
		return float4(0.0, 0.0, 0.0, 0.0);
	}

	return GlowShared(dist);
}

technique GlowInner
{
	pass
	{
		vertex_shader = VSDefault(v_in);
		pixel_shader = PSGlowInner(v_in);
	}
}
// -------------------------------------------------------------------------------- //

// -------------------------------------------------------------------------------- //
// Outline
uniform float4 pOutlineColor <
	string name = "Outline Color";
	string group = "Outline";
>;
uniform float pOutlineWidth <
	string name = "Outline Width";
	string group = "Outline";
	float minimum = 0.01;
	float maximum = 16.0;
	float delta = 0.01;
	float default = 1.0;
>;
uniform float pOutlineOffset <
	string name = "Outline Offset";
	string group = "Outline";
	float minimum = -16.0;
	float maximum = 16.0;
	float delta = 0.01;
	float default = 0.0;
>;
uniform float pOutlineSharpness <
	string name = "Outline Sharpness";
	string group = "Outline";
	float minimum = 0.01;
	float maximum = 100.0;
	float delta = 0.01;
	float default = 1.0;
	string set_expr = "x / 100.0";
>;
uniform float pOutlineSharpnessInverse <
	string name = "Outline Sharpness Inverse";
	string group = "Outline";
	string value_expr = "1.0 / (1.0 - pOutlineSharpness)";
	bool visible = false;
	bool enabled = false;
>;

float4 PSOutline(VertDataOut v_in) : TARGET
{
	float2 iodist = pSDFTexture.Sample(sdfSampler, v_in.uv).rg * MAX_DISTANCE;
	float dist = iodist.r - iodist.g;
	
	// Calculate where we are in the outline.
	// We can use any of the following gradient functions: https://www.desmos.com/calculator/bmbrncaiem
	/// Base Curve
	float n = clamp(abs(dist - pOutlineOffset) / pOutlineWidth, 0.0, 1.0);
	/// Sharpness Curve
	float y1 = clamp((n - pOutlineSharpness) * pOutlineSharpnessInverse, 0.0, 1.0);
	float y2 = cos((y1 - 1) * PI) * 0.5 + 0.5;
	float y3 = sin(y1 * (PI / 2));
	float y4 = sin((y1 - 1) * (PI / 2)) + 1.0;

	// Blend by Color.a so that our outline doesn't delete information.
	float l = 1.0 - ((1.0 - y1) * pOutlineColor.a);

	return float4(pOutlineColor.r, pOutlineColor.g, pOutlineColor.b, pOutlineColor.a * (1.0 - y1));
}

technique Outline
{
	pass
	{
		vertex_shader = VSDefault(v_in);
		pixel_shader = PSOutline(v_in);
	}
}
// -------------------------------------------------------------------------------- //

