#T#PoiVertHullDomainPragma
#pragma vertex VertexProgram
#pragma hull HullProgram
#pragma domain DomainProgram

#T#PoiTessellationDefines
#define POI_TESSELLATED

#T#PoiVertHullDomainStructs
struct tessAppData
{
	float4 vertex : POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
	float4 color : COLOR;
	float2 uv0 : TEXCOORD0;
	float2 uv1 : TEXCOORD1;
	float2 uv2 : TEXCOORD2;
	float2 uv3 : TEXCOORD3;
	uint vertexId : TEXCOORD4;
	UNITY_VERTEX_INPUT_INSTANCE_ID
	UNITY_VERTEX_OUTPUT_STEREO
};

#T#PoiTessellationProperties
[HideInInspector] m_tessellationCategory ("Tessellation", Float) = 0
[ThryWideEnum(Uniform, 0, Edge Length, 1, Distance, 2)] _TessellationType ("Tessellation Type", Int) = 1
_TessellationUniform ("Uniform--{condition_showS:(_TessellationType==0||_TessellationType==2)}", Range(0, 5)) = 2.5
[VectorLabel(Min, Max)] _TessellationDistance ("Distance--{condition_showS:(_TessellationType==2)}", Vector) = (2, 8, 0, 0)
_TessellationEdgeLength ("Edge Length (px)--{condition_showS:(_TessellationType==1)}", Range(10, 60)) = 30
_TessellationPhong ("Phong", Range(0, 1)) = 0.0

#T#PoiVertHullDomainProgram
// Modified from Unity's Tessellation.cginc
float UnityCalcDistanceTessFactor (float3 wpos, float minDist, float maxDist, float tess)
{
	float dist = distance (wpos, _WorldSpaceCameraPos);
	float f = clamp(1.0 - (dist - minDist) / (maxDist - minDist), 0.01, 1.0) * tess;
	return f;
}

float4 UnityCalcTriEdgeTessFactors (float3 triVertexFactors)
{
	float4 tess;
	tess.x = 0.5 * (triVertexFactors.y + triVertexFactors.z);
	tess.y = 0.5 * (triVertexFactors.x + triVertexFactors.z);
	tess.z = 0.5 * (triVertexFactors.x + triVertexFactors.y);
	tess.w = (triVertexFactors.x + triVertexFactors.y + triVertexFactors.z) / 3.0f;
	return tess;
}

float UnityCalcEdgeTessFactor (float3 wpos0, float3 wpos1, float edgeLen)
{
	// distance to edge center
	float dist = distance (0.5 * (wpos0+wpos1), _WorldSpaceCameraPos);
	// length of the edge
	float len = distance(wpos0, wpos1);
	// edgeLen is approximate desired size in pixels
	float f = max(len * _ScreenParams.y / (edgeLen * dist), 1.0);
	return f;
}

float UnityDistanceFromPlane (float3 pos, float4 plane)
{
	float d = dot (float4(pos,1.0f), plane);
	return d;
}

// Returns true if triangle with given 3 world positions is outside of camera's view frustum.
// cullEps is distance outside of frustum that is still considered to be inside (i.e. max displacement)
bool UnityWorldViewFrustumCull (float3 wpos0, float3 wpos1, float3 wpos2, float cullEps)
{
	float4 planeTest;

	// left
	planeTest.x = (( UnityDistanceFromPlane(wpos0, unity_CameraWorldClipPlanes[0]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos1, unity_CameraWorldClipPlanes[0]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos2, unity_CameraWorldClipPlanes[0]) > -cullEps) ? 1.0f : 0.0f );
	// right
	planeTest.y = (( UnityDistanceFromPlane(wpos0, unity_CameraWorldClipPlanes[1]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos1, unity_CameraWorldClipPlanes[1]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos2, unity_CameraWorldClipPlanes[1]) > -cullEps) ? 1.0f : 0.0f );
	// top
	planeTest.z = (( UnityDistanceFromPlane(wpos0, unity_CameraWorldClipPlanes[2]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos1, unity_CameraWorldClipPlanes[2]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos2, unity_CameraWorldClipPlanes[2]) > -cullEps) ? 1.0f : 0.0f );
	// bottom
	planeTest.w = (( UnityDistanceFromPlane(wpos0, unity_CameraWorldClipPlanes[3]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos1, unity_CameraWorldClipPlanes[3]) > -cullEps) ? 1.0f : 0.0f ) +
				  (( UnityDistanceFromPlane(wpos2, unity_CameraWorldClipPlanes[3]) > -cullEps) ? 1.0f : 0.0f );

	// has to pass all 4 plane tests to be visible
	return !all (planeTest);
}

// Distance based tessellation:
// Tessellation level is "tess" before "minDist" from camera, and linearly decreases to 1
// up to "maxDist" from camera.
float4 UnityDistanceBasedTess (float3 v0, float3 v1, float3 v2, float minDist, float maxDist, float tess)
{
	float3 f;
	f.x = UnityCalcDistanceTessFactor (v0,minDist,maxDist,tess);
	f.y = UnityCalcDistanceTessFactor (v1,minDist,maxDist,tess);
	f.z = UnityCalcDistanceTessFactor (v2,minDist,maxDist,tess);

	return UnityCalcTriEdgeTessFactors (f);
}
// End of (Modified) Unity's Tessellation.cginc

struct TessellationFactors
{
	float edge[3] : SV_TessFactor;
	float inside : SV_InsideTessFactor;
};

tessAppData VertexProgram(appdata v)
{
	tessAppData t;
	UNITY_SETUP_INSTANCE_ID(v);
	UNITY_INITIALIZE_OUTPUT(tessAppData, t);
	UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(t);

	t.vertex = v.vertex;
	t.normal = v.normal;
	t.tangent = v.tangent;
	t.color = v.color;
	t.uv0 = v.uv0;
	t.uv1 = v.uv1;
	t.uv2 = v.uv2;
	t.uv3 = v.uv3;
	t.vertexId = v.vertexId;

	return t;
}

float _TessellationType;
float _TessellationUniform;
float2 _TessellationDistance;
float _TessellationEdgeLength;
float _TessellationPhong;

TessellationFactors PatchConstFunc(InputPatch < tessAppData, 3 > patch)
{
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[0]);
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[1]);
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[2]);
	float3 pos0 = mul(unity_ObjectToWorld, patch[0].vertex).xyz;
	float3 pos1 = mul(unity_ObjectToWorld, patch[1].vertex).xyz;
	float3 pos2 = mul(unity_ObjectToWorld, patch[2].vertex).xyz;
	TessellationFactors f;
	if (UnityWorldViewFrustumCull(pos0, pos1, pos2, 0))
	{
		f = (TessellationFactors)0;
	}
	else
	{
		switch (_TessellationType)
		{
			case 0:
				f = (TessellationFactors)_TessellationUniform;
				break;
			case 1:
				f.edge[0] = UnityCalcEdgeTessFactor(pos1, pos2, _TessellationEdgeLength);
				f.edge[1] = UnityCalcEdgeTessFactor(pos2, pos0, _TessellationEdgeLength);
				f.edge[2] = UnityCalcEdgeTessFactor(pos0, pos1, _TessellationEdgeLength);
				f.inside  = (f.edge[0] + f.edge[1] + f.edge[2]) * 0.333333333f;
				break;
			case 2:
				float4 tess = UnityDistanceBasedTess(pos0, pos1, pos2, _TessellationDistance.x, _TessellationDistance.y, _TessellationUniform);
				f.edge[0] = tess[0];
				f.edge[1] = tess[1];
				f.edge[2] = tess[2];
				f.inside  = tess[3];
				break;
			default:
				f = (TessellationFactors)0;
				break;
		}
	}
	return f;
}


[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("fractional_odd")]
[UNITY_patchconstantfunc("PatchConstFunc")]
tessAppData HullProgram(InputPatch < tessAppData, 3 > patch,
uint id : SV_OutputControlPointID)
{
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[id]);
	return patch[id];
}

[UNITY_domain("tri")]
VertexOut DomainProgram(
	TessellationFactors factors,
	OutputPatch < tessAppData, 3 > patch,
	float3 barycentrCoords : SV_DomainLocation,
	uint pid : SV_PrimitiveID
)
{
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[0]);
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[1]);
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(patch[2]);

	tessAppData data;
	PoiInitStruct(tessAppData, data);
	#define DOMAIN_INTERPOLATE(fieldName) data.fieldName = \
			patch[0].fieldName * barycentrCoords.x + \
			patch[1].fieldName * barycentrCoords.y + \
			patch[2].fieldName * barycentrCoords.z;

	data.vertexId = patch[0].vertexId + patch[1].vertexId + patch[2].vertexId;
	DOMAIN_INTERPOLATE(vertex);
	float3 vertex[3];
	[unroll(3)]
	for (int i = 0; i < 3; ++ i)
	{
		vertex[i] = data.vertex.xyz - patch[i].normal * (dot(data.vertex.xyz, patch[i].normal) - dot(patch[i].vertex.xyz, patch[i].normal));
	}
	data.vertex.xyz = _TessellationPhong * (vertex[0] * barycentrCoords.x + vertex[1] * barycentrCoords.y + vertex[2] * barycentrCoords.z) + (1.0f - _TessellationPhong) * data.vertex.xyz;

	DOMAIN_INTERPOLATE(normal);
	DOMAIN_INTERPOLATE(tangent);
	DOMAIN_INTERPOLATE(color);
	DOMAIN_INTERPOLATE(uv0);
	DOMAIN_INTERPOLATE(uv1);
	DOMAIN_INTERPOLATE(uv2);
	DOMAIN_INTERPOLATE(uv3);

	UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(patch[0], data)
	
	return vert(data);
}