/************************************************************
 * Point list geometry shader-based foliage shader			*
 * by Joel Sjöqvist ("Rudolpho")							*
 *															*
 * Included with the foliage example project for the		*
 * Ziggurat (DX11 plugin) project.							*
 ************************************************************/

// This is a constant buffer that will be set by the engine for each rendering pass
cbuffer MatrixBuffer : register(b0) {
	// Precomputed on a per-frame basis
	float4x4 View;
	float4x4 Projection;
	float4x4 ViewProj;			
};

cbuffer PerFrameBuffer : register(b1) {
	float	TimeSine;			// Precomputed sin(time); updated by the engine on a per-frame basis
	float	CutoffLow;			// Any grass control point instance with a Y position below this will not be expanded into geometry
	float	CutoffHigh;			// Same as the above but higher Y coordinates than this will also be clipped
};

// Textures / samplers
Texture2D DiffuseMap;
// Note that samplers are created from DBPro by creating and assigning sampler state resources to the shader technique containing this shader.
// They cannot be created directly in the hlsl file by using the "sampler = sampler_state { ... };" syntax since the old effects framework that 
// permitted this has since been deprecated by Microsoft and thus isn't supported by the DX11 plugin.
sampler DiffuseSampler;

// Transform data is supplied by the Ziggurat engine using a special StructuredBuffer bound to register t24
struct sInstanceTransform {
	float4x4 World;
	float4x4 WorldViewProjection;
};

StructuredBuffer<sInstanceTransform> Transform : register(t24);


// Input from the program to the vertex shader (corresponds to the used vertex layout of the rendered mesh)
struct vs_in {
	float3		pivot		: ANCHOR;
	float2		size		: DIMENSIONS;
	float		angleY		: ANGLE;
	float		bendOffset	: BENDOFFSET;
	// We need the instance id to index into the proper Transform element. 
	// Specifying the below semantic will cause the input assembler to provide us with this value.
	uint		iId			: SV_INSTANCEID;
};

// Output from the vertex shader, input to the geometry shader
struct vs_out {
	float3		pivot		: ANCHOR;
	float2		size		: DIMENSIONS;
	float		angleY		: ANGLE;
	float		bendOffset	: BENDOFFSET;
	// In order to properly index into the Transform buffer from the geometry shader we need to forward 
	// the instance id from the vertex shader
	uint		iId			: INSTANCEID;
};

// Output from the geometry shader, input to the pixel shader
struct gs_out {
	float4 pos		: SV_POSITION;
	float4 colour	: COLOR;
	float2 texcoord : TEXCOORD;
};


// Helper function; creates a rotation matrix that only rotates around the Y axis
float3x3 BuildRotationMatrixY(float y) {
	return float3x3(
		float3(		cos(y),		0,				sin(y)		), 
		float3(		0,			1,				0			), 
		float3(		-sin(y),	0,				cos(y)		)
	);
}


/**
 * The vertex shader is simply a so-called pass through-shader; it forwards the vertex / control point
 * data to the geometry shader.
 **/
vs_out VS(vs_in IN) {
	vs_out OUT;

	OUT.pivot		= IN.pivot;
	OUT.size		= IN.size;
	OUT.angleY		= IN.angleY;
	OUT.bendOffset	= IN.bendOffset;
	OUT.iId			= IN.iId;

    return OUT;
}

/**
 * This is the geometry shader which is responsible for the majority of the work in this shader set.
 * It creates new vertices in order to form grass tufts consisting of two crossed planes from each input
 * "vertex" (control point) passed on from the vertex shader and appends these to an "inout" stream argument.
 * This is different from the vertex and pixel shader stages which simply return their results. Using a stream of 
 * geometry shader output allows us to insert zero or more vertices (which is what the geometry shader produces).
 * Not appending anything to the stream will result in no geometry being added, and consequently clips grass tufts
 * in this implementation since the input vertices (control points) will not themselves be forwarded to the pixel shader.
 * In this way the geometry shader can be used to both increase and decrease the number of drawn vertices.
 * 
 * The odd syntax appearing above the shader function declaration is called an «attribute». These are used to provide
 * constant information that cannot be changed during runtime to DirectX. This one tells that the geometry shader will 
 * at most output 8 vertices per input vertex / control point and as such the system can optimize the stream for this.
 * It is also perfectly possible to append anything less than 8 vertices, as stated above.
 * 
 * The input to the geometry shader is an array of «points» ("vertices"). How many elements will be provided in this array 
 * depends on the input topology; for our grass objects it will be one, making the geometry shader be executed for each point.
 * A triangle mesh would receive three points, corresponding to a triangle face, and so on.
 **/
[maxvertexcount(8)]
void GS(point vs_out input[1], inout TriangleStream<gs_out> stream) {
	static const float PI		= 3.14159265;
	static const float HALF_PI	= 1.570796325;
	
	// Clip grass which elevation is too high / low for a more natural look
	if(input[0].pivot.y < CutoffLow || input[0].pivot.y > CutoffHigh)
		return;

	// Vertex data; the gs_out instance will be updated and appended to the output stream multiple times so we just need the one.
	// The vert array will contain the different vertex positions.
	gs_out	v;
	float3 vert[8];

	// Add a blended colour based on how close to the tree line this grass tuft is; make it more pale the higher its altitude
	float4 col = float4(0.7, 0.5, 0.4, 0.0) * (input[0].pivot.y * 0.31 / CutoffHigh);

	// Direction vectors used to offset the vertices from the pivot point
	float3 vecRight = float3(1.0, 0.0, 0.0) * input[0].size.x * 0.5;
	float3 vecUp	= float3(0.0, 1.0, 0.0) * input[0].size.y;
	
	// Rotate right vector around the Y axis based on the input angle
	float angle = input[0].angleY;
	float3 newRight = mul(vecRight, BuildRotationMatrixY(angle));

	// We get the points by using the billboards anchor position and the up and right vectors (which are pre-multiplied with the grass' width / height)
	vert[0] = input[0].pivot.xyz - newRight;
	vert[1] = input[0].pivot.xyz + newRight;
	vert[2] = input[0].pivot.xyz - newRight + vecUp;
	vert[3]	= input[0].pivot.xyz + newRight + vecUp;

	// Rotate the right vector 90° so that the second quad will cross the first
	newRight = mul(vecRight, BuildRotationMatrixY(angle + HALF_PI));

	vert[4] = input[0].pivot.xyz - newRight;
	vert[5] = input[0].pivot.xyz + newRight;
	vert[6] = input[0].pivot.xyz - newRight + vecUp;
	vert[7]	= input[0].pivot.xyz + newRight + vecUp;

	// Offset top vertices based on the TimeSine value to create a cheap illusion of the grass moving in the wind
	float3 offset = float3(0, 0, sin(input[0].bendOffset + TimeSine) * 0.25);
	vert[2] += offset;
	vert[3] += offset;
	vert[6] += offset;
	vert[7] += offset;

	// Get billboards texture coordinates
	float2 texCoord[4];
	texCoord[0] = float2(0, 1);
	texCoord[1] = float2(1, 1);
	texCoord[2] = float2(0, 0);
	texCoord[3] = float2(1, 0);

	// Create first quad
	for(int i = 0; i < 4; i++) {
		v.pos = mul(float4(vert[i], 1.0f), Transform[input[0].iId].WorldViewProjection);
		v.texcoord = texCoord[i];
		v.colour = col;
		// This adds our vertex 'v' to the output stream
		stream.Append(v);
	}
	// The geometry shader outputs a stream with the triangle strip topology.
	// This essentially means that two triangles following each other in the index buffer will be considered to have shared
	// vertices and thus connected. This allows us to for example draw a quad with just 4 vertices (/indices) instead of 6.
	// In order to draw disconnected faces, the strip must be closed and a new one started however. This function will do that
	// so that the both quads making up a grass tuft will be separate and thus can be crossed. 
	// Note that a new strip is always begun foreach geometry shader invocation so we don't need to call it at the end.
	stream.RestartStrip();
	// Create second quad
	for(int i = 0; i < 4; i++) {
		v.pos = mul(float4(vert[i + 4], 1.0f), Transform[input[0].iId].WorldViewProjection);
		v.texcoord = texCoord[i];
		v.colour = col;
		stream.Append(v);
	}
}

/**
 * The pixel shader is pretty straight forward; it will receive all vertices created by the geometry shader and 
 * apply the grass texture to them, as well as add the "paling" colour computed in the geometry shader to the 
 * sampled texture pixel colour.
 * Alpha testing is used to simply clip any pixel whose alpha value is less than 0.5.
 **/
float4 PS(gs_out IN) : SV_TARGET {
	float4 diffuse = DiffuseMap.Sample(DiffuseSampler, IN.texcoord) + IN.colour;
	clip(diffuse.a - 0.5);
	return diffuse;
}
