///////////////////////////////////////////////////////////////////////  
//  Billboard.hlsl
//
//	*** INTERACTIVE DATA VISUALIZATION (IDV) CONFIDENTIAL AND PROPRIETARY INFORMATION ***
//
//	This software is supplied under the terms of a license agreement or
//	nondisclosure agreement with Interactive Data Visualization, Inc. and
//  may not be copied, disclosed, or exploited except in accordance with 
//  the terms of that agreement.
//
//      Copyright (c) 2003-2010 IDV, Inc.
//      All rights reserved in all media.
//
//      IDV, Inc.
//      Web: http://www.idvinc.com

#ifndef SPEEDTREE_USING_EFFECTS
	#include "Global.hlsl"
	#include "UniformVariables.hlsl"
	#include "Utility.hlsl"
	#include "Wind.hlsl"
#endif


///////////////////////////////////////////////////////////////////////  
//  Billboard-specific constant tables

// used to decompress the g_v360TexCoord lookups (x = s scale, y = t scale)
const float2 c_avVertTexCoordScales[4] =				
{ 
	float2(1.0f, 0.0f), 
	float2(0.0f, 0.0f), 
	float2(0.0f, 1.0f), 
	float2(1.0f, 1.0f)
};

// used to decompress the g_v360TexCoord lookups (x = s scale, y = t scale)
const float2 c_avHorzTexCoordScales[4] =				
{ 
	float2(1.0f, 1.0f),  
	float2(0.0f, 1.0f), 
	float2(0.0f, 0.0f), 
	float2(1.0f, 0.0f)
};


///////////////////////////////////////////////////////////////////////  
//  Billboard-specific global variables

// SPEEDTREE_MAX_BILLBOARD_IMAGES should be defined during shader compilation by the
// app or by an offline utility; if not set it should default to something
#ifndef SPEEDTREE_MAX_BILLBOARD_IMAGES
	#define SPEEDTREE_MAX_BILLBOARD_IMAGES 50
#endif

float		g_fNum360Images                                  GLOBAL_REG(50);
float4      g_vCameraAzimuthTrig                             GLOBAL_REG(51);  // stores (sin, cos, -sin, 0.0) for the camera azimuth angle for optimized rotation in billboard vs
float4		g_vHorzBillboardTexCoords                        GLOBAL_REG(52);  
float4		g_vVertBillboardDimensions					     GLOBAL_REG(53);  // x = half_width, y = -half_width, z = top, z = bottom
float4		g_vHorzBillboardDimensions                       GLOBAL_REG(54);  // x = min x, y = max x, z = min y, w = max y
float4		g_avBillboardTangents[3]                         GLOBAL_REG(55);  // w components are ignored
float4		g_av360TexCoords[SPEEDTREE_MAX_BILLBOARD_IMAGES] GLOBAL_REG(58);


///////////////////////////////////////////////////////////////////////  
//  Shader input/output structures
//
//	The cpp-level source code is setup to feed this vertex format to all of the
//	vertical BB vertex shader variations.

struct SBillboardVertex
{
	// vertical vertex attributes
	float4	m_vPosition			: SPEEDTREE_ATTR0;	// xyz = center pos, w = encoded(corner index, rotation)
	float	m_fScale			: SPEEDTREE_ATTR8;	// scale

	// horizontal billboard notes:
	//   - if corner index (m_vPosition.w) is greater than 3, then a horizontal billboard is being drawn
	//   - billboard with and height parameters are not used
};

struct SBillboardPixel
{
    float4  m_vPosition			: SPEEDTREE_VS_OUTPUT;	// xyz = position
	float4	m_vFogData			: COLOR0;				// used to help with fog & sky effect
	float2	m_vTexCoords		: TEXCOORD0;			// xy = texcoords_image_0
	float1	m_vAlphaScalar		: TEXCOORD1;			// x = alpha scalar
	float3	m_vNormalMapVec		: TEXCOORD2;			// xyz = normal map adjust
};


///////////////////////////////////////////////////////////////////////  
//  Helper function: RotateCorner

float3 RotateCorner(float3 vCorner, float4 vRotationValues)
{
	#ifdef SPEEDTREE_Z_UP
		return float3(dot(g_vCameraAzimuthTrig.yxw, vCorner.xyz), dot(g_vCameraAzimuthTrig.zyw, vCorner.xyz), vCorner.z);
	#else
		return float3(dot(g_vCameraAzimuthTrig.ywz, vCorner.xyz), vCorner.y, dot(g_vCameraAzimuthTrig.xwy, vCorner.xyz));
	#endif
}


///////////////////////////////////////////////////////////////////////  
//  Helper function: DetermineBillboardTexCoords
//
//	The SpeedTree Compiler application uses the following convention when generating
//	the 360-degree billboard images.
//
//	The N billboard images are taken from angles (0/N)*360, (1/N)*360, and ((N-1)/N)*360.
//  0 degrees is when the camera is at a normalized position of (1,0,0), looking to the 
//	origin.  90 degrees is when the camera is at a normalized position of (0,1,0), looking 
//  to the origin and so forth.
//
//	One 360-degree image is meant to cover a 360/N-degree sweep.  For example, if 6 images
//	are used, image 0 will cover -30 to 30 degrees, image 1 covers 30 to 90, etc.

float2 DetermineBillboardTexCoords(float fRotation, int nCornerIndex) // rotation is in radians
{
	// values derived from global shader constants
    const float  c_fCameraAzimuth = g_vCameraAngles.x;
	const float	 c_nNum360Images = g_fNum360Images;
    const float  c_fAngleSweepPerImage = c_fTwoPi / float(c_nNum360Images);
	const float  c_fHalfSweep = 0.5f * c_fAngleSweepPerImage;

	// get the tree angle into [0, 2pi] range
	float fTreeAngle = c_fCameraAzimuth + fRotation + c_fHalfSweep + c_fPi;

	#if (defined(SPEEDTREE_RHCS) && defined(SPEEDTREE_Y_UP))
		fTreeAngle += c_fPi;
	#endif
	#if (defined(SPEEDTREE_LHCS) && defined(SPEEDTREE_Z_UP))
		fTreeAngle = c_fPi - fTreeAngle + c_fHalfSweep;
	#endif
	#if (defined(SPEEDTREE_LHCS) && defined(SPEEDTREE_Y_UP))
		fTreeAngle = c_fTwoPi - fTreeAngle + 2.0f * c_fHalfSweep;
	#endif

	fTreeAngle = fmod(fTreeAngle, c_fTwoPi);
	if (fTreeAngle < 0.0f)
		fTreeAngle += c_fTwoPi;

	// convert from tree angle to texcoord index
	int nImageIndex = trunc(fTreeAngle / c_fAngleSweepPerImage);

	// this shouldn't be necessary if fmod() worked correctly, but roundoff
	// may possibly cause a problem
	//if (nImageIndex > c_nNum360Images - 1)
	//	nImageIndex = 0;

	// compute texcoords
	const float4 vTexCoords = g_av360TexCoords[nImageIndex];

	return vTexCoords.xy - (vTexCoords.zw * c_avVertTexCoordScales[nCornerIndex].xy);
}


///////////////////////////////////////////////////////////////////////  
//  Helper function: DecodeCornerAndRotation

void DecodeCornerAndRotation(float fEncodedValue, out int nCorner, out float fRotation)
{
	const float c_fFracRange = 0.95;

	nCorner = trunc(fEncodedValue);
	fRotation = c_fTwoPi * frac(fEncodedValue) / c_fFracRange;
}


///////////////////////////////////////////////////////////////////////  
//  BillboardVertex

SBillboardPixel BillboardVertex(SBillboardVertex sIn)
{
    // this will be fed to the branch pixel shader
    SBillboardPixel sOut;

	// setup aliases (taken from global variables)
	const float	 c_fLodNear = g_vLodProfile.z;
	const float	 c_fLodRange = g_vLodProfile.w - g_vLodProfile.z;
	const float	 c_nNum360Images = g_fNum360Images;
	const float4 c_vHorzTexCoords = g_vHorzBillboardTexCoords;

	// setup shared bb aliases (taken from incoming vertex); vertical and horizontal
	// shader paths may used these a little differently
	const float3 c_vPosition = sIn.m_vPosition.xyz;
	const float  c_fScale = sIn.m_fScale;

	// decode corner index and rotation from single float value in sIn.m_vPosition.w
	int	nCornerIndex;
	float fRotation;
	DecodeCornerAndRotation(sIn.m_vPosition.w, nCornerIndex, fRotation);

	// determine vertical or horizontal based on corner index (verts are 0-3, horz are 4-7)
	const bool c_bHorizontal = (nCornerIndex > 3);

	// compute LOD value
	float fDistance = distance(g_vCameraPosition, c_vPosition);
	float fVisibilityScale = fDistance < c_fLodNear ? 0.0 : c_fScale;

	// compute fade value (for transition to/from 3d geometry)
	sOut.m_vAlphaScalar.x = ComputeBillboardFade(fDistance);

	float3 vNormal, vBinormal, vTangent, vPosition, vCorner;
	#ifdef SPEEDTREE_HORZ_BBS_ACTIVE
		if (c_bHorizontal)
		{
			// pick pair of coords based on corner index
			float2 vCornerCoords;
			if (nCornerIndex == 4)
				vCornerCoords = g_vHorzBillboardDimensions.xz;
			else if (nCornerIndex == 5)
				vCornerCoords = g_vHorzBillboardDimensions.yz;
			else if (nCornerIndex == 6)
				vCornerCoords = g_vHorzBillboardDimensions.yw;
			else
				vCornerCoords = g_vHorzBillboardDimensions.xw;

			// setup tangents for horizontal bb
			vNormal = ConvertFromStd(float3(0.0f, 0.0f, 1.0f));
			vTangent = ConvertFromStd(float3(1.0f, 0.0f, 0.0f));
			vBinormal = ConvertFromStd(float3(0.0f, 1.0f, 0.0f));

			// rotate the tangents into place (no need to multiply normal since it points up)
			float3x3 matRotation = RotationMatrix_AroundUpAxis(-fRotation);
			vTangent = mul(matRotation, vTangent);
			vBinormal = mul(matRotation, vBinormal);

			// rotate the corner into place
			vCorner = ConvertFromStd(float3(vCornerCoords.xy, 0.0f));
			vCorner = mul(matRotation, vCorner);

			if (g_fHorzBillboardFade == 0.0f)
				fVisibilityScale = 0.0f;

			vPosition = c_vPosition + vCorner * fVisibilityScale;

			// texcoords in place (last entry in the texture table holds horz coords)
			sOut.m_vTexCoords.xy =  c_vHorzTexCoords.xy - (c_vHorzTexCoords.zw * c_avHorzTexCoordScales[nCornerIndex - 4].xy);

			// compute fade value (for transition to/from 3d geometry)
			sOut.m_vAlphaScalar.x *= g_fHorzBillboardFade;
		}
		else
		{
	#endif // SPEEDTREE_HORZ_BBS_ACTIVE
			// pick pair of coords based on corner index
			float2 vCornerCoords;
			if (nCornerIndex == 0)
				vCornerCoords = g_vVertBillboardDimensions.xz;
			else if (nCornerIndex == 1)
				vCornerCoords = g_vVertBillboardDimensions.yz;
			else if (nCornerIndex == 2)
				vCornerCoords = g_vVertBillboardDimensions.yw;
			else
				vCornerCoords = g_vVertBillboardDimensions.xw;

			// scale corner (from 0.0 [invisible] while in 3D mode to visible in billboard)
			#ifdef SPEEDTREE_Z_UP
				vCorner = float3(0.0f, vCornerCoords) * fVisibilityScale;
			#else
				vCorner = float3(0.0f, vCornerCoords.yx) * fVisibilityScale;
			#endif

			// rotate corner
			vCorner = RotateCorner(vCorner, g_vCameraAzimuthTrig);

			// pick the right billboard out of the atlas, based on this instance's rotation value
			sOut.m_vTexCoords.xy = DetermineBillboardTexCoords(fRotation, nCornerIndex);
			
			#ifdef SPEEDTREE_BASIC_WIND
				vCorner = BillboardMotion(vCorner / c_fScale, c_vPosition.xy) * c_fScale;
			#endif

			// compute final world-space position
			vPosition = c_vPosition + vCorner;

			// setup tangents
			vNormal = g_avBillboardTangents[0].xyz;
			vBinormal = g_avBillboardTangents[1].xyz;
			vTangent = g_avBillboardTangents[2].xyz;

			// compute fade value (for transition to/from 3d geometry)
			sOut.m_vAlphaScalar.x *= (1.0f - g_fHorzBillboardFade);
	#ifdef SPEEDTREE_HORZ_BBS_ACTIVE
		}
	#endif

	// compute normal mapping vector
	sOut.m_vNormalMapVec.x = dot(g_vLightDir, vTangent);
    sOut.m_vNormalMapVec.y = dot(g_vLightDir, vBinormal);
    sOut.m_vNormalMapVec.z = dot(g_vLightDir, vNormal);

	// final screen projection
	sOut.m_vPosition = ProjectToScreen(vPosition.xyz);
	
	// compute fog value
    sOut.m_vFogData = FogVertex(vPosition.xyz);	

	return sOut;
}


///////////////////////////////////////////////////////////////////////  
//  BillboardPixel

float4 BillboardPixel(SBillboardPixel sIn) : SPEEDTREE_PS_OUTPUT
{
	// setup aliases (taken from incoming data)
	const float2 c_vTexCoords = sIn.m_vTexCoords.xy;

	// setup aliases (taken from global variables)
	const float4 c_vAmbientLight = g_vMaterialAmbient;
	const float4 c_vDiffuseLight = g_vMaterialDiffuse;
	const float  c_fLightScalar = g_vMaterialExtras.x;

	// texture lookups
	float4 texDiffuseTexture = SampleTexture(Diffuse, c_vTexCoords);
	float4 texNormalTexture = SampleTexture(Normal, c_vTexCoords);

	// pull out different parts of the textures
	float3 vDiffuseMap = texDiffuseTexture.rgb;
	float fAlphaMask = texDiffuseTexture.a;
	float3 vNormalMap = texNormalTexture.rgb;
	float fAmbient = texNormalTexture.a;

	// compute alpha scalar
	float fAlphaScalar = sIn.m_vAlphaScalar.x;
	#ifdef SPEEDTREE_ALPHA_TESTING
		// introduce noise into the alpha channel to make alpha tested transitions smoother
		fAlphaScalar -= (1.0 - fAlphaScalar) * AlphaTestNoise(c_fBillboardNoiseScalar * c_vTexCoords);
	#endif
	float fAlphaOut = fAlphaMask * fAlphaScalar;
	#ifdef SPEEDTREE_ALPHA_TESTING
		fAlphaOut -= 0.1;
	#endif
	PossibleEarlyOut(fAlphaOut);

	// lighting computation
	float3 vNormal = DecodeVectorFromColor(vNormalMap);
	float fNormalMapLighting = dot(vNormal, normalize(sIn.m_vNormalMapVec));

	// compute diffuse contribution
	float3 vDiffuseComponent = c_vDiffuseLight.rgb;

	// compute ambient contribution
	const float c_fAmbientContrastParameter = g_vMaterialExtras.w;
	float fAmbientContrast = lerp(c_fAmbientContrastParameter, 1.0, abs(fNormalMapLighting));
	float3 vAmbientComponent = fAmbient * c_vAmbientLight.rgb * fAmbientContrast;

	fNormalMapLighting = saturate(fNormalMapLighting);

	// get shadow contribution if enabled
	#if defined(SPEEDTREE_SHADOWS_ENABLED) && defined(SPEEDTREE_BILLBOARDS_RECEIVE_SHADOWS)
		fNormalMapLighting *= ShadowMapLookup(sIn.m_av2dPosInLightSpace, sIn.m_fDepth);
	#endif

	// compute whole light
	float3 vOutColor = lerp(vAmbientComponent, vDiffuseComponent * c_fLightScalar, fNormalMapLighting);
	vOutColor *= vDiffuseMap;

	// fog the output (non-trivial with sky contribution)
	vOutColor = FogPixel(sIn.m_vFogData, vOutColor);

	return float4(vOutColor, fAlphaOut);
}


#ifdef SPEEDTREE_Z_PREPASS

	///////////////////////////////////////////////////////////////////////  
	//  BillboardVertex_Depth

	SBillboardPixel BillboardVertex_Depth(SBillboardVertex sIn)
	{
		return BillboardVertex(sIn);
	}


	///////////////////////////////////////////////////////////////////////  
	//  BillboardPixel_Depth

	float4 BillboardPixel_Depth(SBillboardPixel sIn) : SPEEDTREE_PS_OUTPUT
	{
		// setup aliases (taken from incoming data)
		const float2 c_vTexCoords = sIn.m_vTexCoords.xy;

		// texture lookups
		float fAlphaMask = SampleTexture(Diffuse, c_vTexCoords).a;

		// compute alpha scalar
		float  fAlphaScalar = sIn.m_vAlphaScalar.x;
		#ifdef SPEEDTREE_ALPHA_TESTING
			// introduce noise into the alpha channel to make alpha tested transitions smoother
			fAlphaScalar -= (1.0 - fAlphaScalar) * AlphaTestNoise(c_fBillboardNoiseScalar * c_vTexCoords);
		#endif
		float fAlphaOut = fAlphaMask * fAlphaScalar;
		#ifdef SPEEDTREE_ALPHA_TESTING
			fAlphaOut -= 0.1;
		#endif
		PossibleEarlyOut(fAlphaOut);

		return float4(1.0f, 1.0f, 1.0f, fAlphaOut);
	}

#endif // SPEEDTREE_Z_PREPASS



