/*=============================================================================
	GpuSkinVertexFactory.hlsl: GPU skinned vertex factory shader code
	Copyright 1998-2009 Epic Games, Inc. All Rights Reserved.

	This code contains embedded portions of source code from dqconv.c Conversion routines between (regular quaternion, translation) and dual quaternion, Version 1.0.0, Copyright © 2006-2007 University of Dublin, Trinity College, All Rights Reserved, which have been altered from their original version.

	The following terms apply to dqconv.c Conversion routines between (regular quaternion, translation) and dual quaternion, Version 1.0.0:

	This software is provided 'as-is', without any express or implied warranty.  In no event will the author(s) be held liable for any damages arising from the use of this software.

	Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

	1. The origin of this software must not be misrepresented; you must not
	   claim that you wrote the original software. If you use this software
	   in a product, an acknowledgment in the product documentation would be
	   appreciated but is not required.
	2. Altered source versions must be plainly marked as such, and must not be
	   misrepresented as being the original software.
	3. This notice may not be removed or altered from any source distribution.


=============================================================================*/

#define MAX_BONES 75

#ifndef GPUSKIN_MORPH_BLEND
#define GPUSKIN_MORPH_BLEND 0
#endif

#ifndef GPUSKIN_DECAL
#define GPUSKIN_DECAL 0
#endif

float ApexDummy; // Just to make sure we don't get the wrong VertexFactory.

#ifndef QUAT_SKINNING
#define QUAT_SKINNING 0
#endif

#if QUAT_SKINNING
#ifndef TRANSFORM_BLEND
#define TRANSFORM_BLEND 1
#endif
#endif

float4x4 LocalToWorld;

// NOTE: We pack the LocalToWorldRotDeterminantFlip into the 4x4th element of the WorldToLocal register in
//		 order to free up a vertex shader constant.  Bone matrices use up a lot of constants so this is crucial!
#if PS3
	float3x4 WorldToLocal;
#else
	float4x3 WorldToLocal;
#endif

// NOTE: To save a vertex shader constant, LocalToWorldRotDeterminantFlip is packed in with WorldToLocal
static const float LocalToWorldRotDeterminantFlip =
#if PS3
	WorldToLocal[0][3];
#else
	WorldToLocal[3][0];
#endif

#if GPUSKIN_DECAL
float4 BoneToDecalRow0;
float4 BoneToDecalRow1;
float3 DecalLocation;
#else
float4x4 PreviousLocalToWorld;
#endif
float3 MeshOrigin;
float3 MeshExtension;

struct FVertexFactoryInput
{
	float4	Position		: POSITION;
	half3	TangentX		: TANGENT;
	// TangentZ.w contains sign of tangent basis determinant
	half4	TangentZ		: NORMAL;	
#if SM4_PROFILE
	// TODO: figure out why conversion to int seems to squish everything
	uint	BlendIndex		: BLENDINDICES0;
#else
	int		BlendIndex		: BLENDINDICES0;
#endif
	float4	BlendWeights	: BLENDWEIGHT0;	
#if NUM_MATERIAL_TEXCOORDS && !(GPUSKIN_DECAL && MATERIAL_DECAL)
	float2	TexCoords[NUM_MATERIAL_TEXCOORDS] : TEXCOORD0;
#endif
#if GPUSKIN_MORPH_BLEND
	// NOTE: TEXCOORD6,TEXCOORD7 used instead of POSITION1,NORMAL1 since those semantics are not supported by Cg 
	/** added to the Position */
	float3	DeltaPosition	: TEXCOORD6;	//POSITION1;
	/** added to the TangentZ and then used to derive new TangentX,TangentY */
	half3	DeltaTangentZ	: TEXCOORD7;	//NORMAL1;
#endif
};

struct FVertexFactoryInterpolants
{
#if WORLD_COORDS
	// First row of the tangent to world matrix
	float3	TangentToWorld0	: TANGENTTOWORLD0;
	// Last row of the tangent to world matrix in xyz
	float4	TangentToWorld2	: TANGENTTOWORLD2;
#endif
#if NUM_MATERIAL_TEXCOORDS
	float2	TexCoords[NUM_MATERIAL_TEXCOORDS]	: TEXCOORD0;
#endif

#if !COMPILER_SUPPORTS_EMPTY_STRUCTS && !WORLD_COORDS && !NUM_MATERIAL_TEXCOORDS
	float4 Dummy : TEXCOORD0;
#endif
};

/** Converts from vertex factory specific interpolants to a FMaterialPixelParameters, which is used by material inputs. */
FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolants Interpolants)
{
	FMaterialPixelParameters	Result;
#if NUM_MATERIAL_TEXCOORDS	
	for(int CoordinateIndex = 0;CoordinateIndex < NUM_MATERIAL_TEXCOORDS;CoordinateIndex++)
		Result.TexCoords[CoordinateIndex] = Interpolants.TexCoords[CoordinateIndex];
#endif
	Result.VertexColor = 1;
	Result.TangentNormal = 0;
	Result.TangentCameraVector = 0;
	Result.TangentReflectionVector = 0;
	Result.ScreenPosition = 0;
	Result.TangentLightVector = 0;
#if WORLD_COORDS
	half3 TangentToWorld0 = UnpackColor3( Interpolants.TangentToWorld0 );
	half4 TangentToWorld2 = UnpackColor4( Interpolants.TangentToWorld2 );
	Result.TangentToWorld = CalcTangentToWorld( TangentToWorld0, TangentToWorld2 );
	Result.UnMirrored = TangentToWorld2.w;	
#endif
#if MATERIAL_DECAL
	Result.DecalAttenuation = 1;
	Result.DecalPlaneDistance = 0;
#endif

#if LIGHTMAP_UV_ACCESS
	Result.LightmapUVs = float2(0,0);
#endif
        Result.TwoSidedSign = 1;
	return Result;
}

#if NEEDS_LIGHTMAP_COORDINATE
float2 GetLightMapCoordinate(FVertexFactoryInterpolants Interpolants)
{
	return float2(0,0);
}

#if NEEDS_SHADOWMAP_COORDINATE
float2 GetShadowMapCoordinate(FVertexFactoryInterpolants Interpolants)
{
	return float2(0,0);
}
#endif
#endif

#if PS3
	#define FBoneMatrix float3x4
#else
	#define FBoneMatrix float4x3
#endif

#if SM4_PROFILE
cbuffer VSBoneConstants : register(b3)
{
#endif

#if QUAT_SKINNING
// Having max const register error when using float2x4
// So using struct 
struct FDualQuaternion
{
	float4	DQ0;
	float4	DQ1;
};
FDualQuaternion BoneQuats[MAX_BONES];
// Bone Scale array
float	 BoneScales[MAX_BONES];
#else
/** skinning matrices stored in 3x4 order */
FBoneMatrix BoneMatrices[MAX_BONES];
#endif

#if SM4_PROFILE
}; // VSBoneConstants
#endif

// Cache data to avoid multiple calculation 
struct FVertexFactoryIntermediates
{
#if TRANSFORM_BLEND
	// BlendDQ data to avoid recalc - I need only one time Scale, so no saving
	float2x4	BlendDQ;
	// Some of below are calculated only once or potentially more than once
	// Maybe we don't need
	// BlendScale
	float BlendScale;
#else
	// Blend Matrix (used for position/tangents)
	FBoneMatrix BlendMatrix;
#endif

	// Unpacked position
	float3 UnpackedPosition;
	
	// Tangent Basis
	float3x3 TangentBasis; 
};

float4 VertexFactoryGetVertexColor(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return 1;
}

/** Converts from vertex factory specific input to a FMaterialVertexParameters, which is used by vertex shader material inputs. */
FMaterialVertexParameters GetMaterialVertexParameters( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3 WorldPosition, float3x3 TangentBasis )
{
	FMaterialVertexParameters Result;
	Result.WorldPosition = WorldPosition - PreViewTranslation.xyz;
	Result.WorldNormal = TangentBasis[2];
	Result.VertexColor = VertexFactoryGetVertexColor(Input, Intermediates);	
	Result.VertexColor = 1;
	return Result;
}

/**
* Unpack position - uncompress xyz position to world position
*/
float3 UnpackedPosition( FVertexFactoryInput Input )
{
	return float3(Input.Position.xyz*MeshExtension + MeshOrigin);
}

#if GPUSKIN_MORPH_BLEND
/** 
* Adds the delta position from the combined morph targets to the vertex position 
*/
float3 MorphPosition( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates )
{
	return Input.Position.xyz + Input.DeltaPosition;
}
#endif

#if QUAT_SKINNING
float4 GetScaledPosition(FVertexFactoryIntermediates Intermediates, float4 Position)
{
	//return Position;
	return Intermediates.BlendScale*Position;
}

/* 
 * Dual Quaternion - http://isg.cs.tcd.ie/projects/DualQuaternions/
 * Blending DQ
 */
float2x4 CalcBlendDQ(FVertexFactoryInput Input)
{
	// Get BlendDQ
	float4 DQ_X = BoneQuats[Input.BlendIndices.x].DQ0;
	float4 DQ_Y = BoneQuats[Input.BlendIndices.y].DQ0;
	float4 DQ_Z = BoneQuats[Input.BlendIndices.z].DQ0;
	float4 DQ_W = BoneQuats[Input.BlendIndices.w].DQ0;

	// SignedBlendWeights - keep shortest route for linear blending
	float4 signedBlendWeights = sign(float4(1.f, dot(DQ_X, DQ_Y), dot(DQ_X, DQ_Z), dot(DQ_X, DQ_W)))*Input.BlendWeights;

	// Blend DQ
	float2x4 BlendDQ;

	BlendDQ[0] = signedBlendWeights.x * BoneQuats[Input.BlendIndices.x].DQ0;
	BlendDQ[0] += signedBlendWeights.y * BoneQuats[Input.BlendIndices.y].DQ0;
	BlendDQ[0] += signedBlendWeights.z * BoneQuats[Input.BlendIndices.z].DQ0;
	BlendDQ[0] += signedBlendWeights.w * BoneQuats[Input.BlendIndices.w].DQ0;

	BlendDQ[1] = signedBlendWeights.x * BoneQuats[Input.BlendIndices.x].DQ1;
	BlendDQ[1] += signedBlendWeights.y * BoneQuats[Input.BlendIndices.y].DQ1;
	BlendDQ[1] += signedBlendWeights.z * BoneQuats[Input.BlendIndices.z].DQ1;
	BlendDQ[1] += signedBlendWeights.w * BoneQuats[Input.BlendIndices.w].DQ1;

	// Normalize
	float rLen = rsqrt(dot(BlendDQ[0], BlendDQ[0]));
	BlendDQ *= rLen;

	return BlendDQ;
}

#if TRANSFORM_BLEND
/* 
 * Dual Quaternion - http://isg.cs.tcd.ie/projects/DualQuaternions/
 * Convert DQ to Matrix
 * This is faster in our case since it calculates DQ result once and reuse it for position/tangents
 * If you use transform, you need to transform at least 3 times - more expensive (a lot of cross products)
 * DQToMatrix does not work with Scale - so I'm saving this for reference in the future
 */
float3 CalcPositionFromDualQuat(FVertexFactoryIntermediates Intermediates, float4 Position)
{
	// get scale 
	float3 ScaledPosition = GetScaledPosition(Intermediates, Position).xyz;

	NOEXPRESSIONOPTIMIZATIONS
	{
		float3 NewPosition = ScaledPosition + 2.0*cross(Intermediates.BlendDQ[0].yzw, cross(Intermediates.BlendDQ[0].yzw, ScaledPosition) + Intermediates.BlendDQ[0].x*ScaledPosition);
		NewPosition += 2.0*(Intermediates.BlendDQ[0].x*Intermediates.BlendDQ[1].yzw - Intermediates.BlendDQ[1].x*Intermediates.BlendDQ[0].yzw + cross(Intermediates.BlendDQ[0].yzw, Intermediates.BlendDQ[1].yzw));
		return NewPosition;
	}
}
#else

/* 
 * Dual Quaternion - http://isg.cs.tcd.ie/projects/DualQuaternions/
 * Convert DQ to Matrix
 * This is faster in our case since it calculates DQ result once and reuse it for position/tangents
 * If you use transform, you need to transform at least 3 times - more expensive (a lot of cross products)
 * DQToMatrix does not work with Scale - so I'm saving this for reference in the future
 */
FBoneMatrix DQToMatrix(float4 Qn, float4 Qd)
{	
	float Len = dot(Qn, Qn);
	float4 Trans = float4(Qd.x, Qd.y, Qd.z, Qd.w);

	float4 Quat = float4(Qn.y, Qn.z, Qn.w, Qn.x);

	float3 XXYYZZ = 2 * Quat.xyz * Quat.xyz;
	float3 XYXZYZ = 2 * Quat.xxy * Quat.yzz;
	float3 XWYWZW = 2 * Quat.xyz * Quat.www;

	float3 T = float3(1,1,1) - XXYYZZ.yxx - XXYYZZ.zzy;
	float3 P = XYXZYZ.xzy + XWYWZW.zxy;
	float3 M = XYXZYZ.yxz - XWYWZW.yzx;

	float4 Quatx2 = 2*Quat;
	
	float3x4 TM = float3x4(
		-Quatx2.x, 	Quatx2.w, 	-Quatx2.z, 	Quatx2.y,
		-Quatx2.y, 	Quatx2.z, 	Quatx2.w, 	-Quatx2.x,
		-Quatx2.z, -Quatx2.y, 	Quatx2.x, 	Quatx2.w
		);
	
	float3 TransPosition = mul(TM, Trans);
		
#if PS3
	FBoneMatrix BoneMatrix = float3x4(    
       T.x,M.y,P.z,TransPosition.x,
	   P.x,T.y,M.z,TransPosition.y,
	   M.x,P.y,T.z,TransPosition.z 
      );
#else
	FBoneMatrix BoneMatrix = float4x3(    
       T.x,P.x,M.x,
       M.y,T.y,P.y,
       P.z,M.z,T.z,
       TransPosition.x,
	   TransPosition.y,
	   TransPosition.z 
       );
#endif

	BoneMatrix /= Len;
	
	return BoneMatrix;	
}

FBoneMatrix CalcBoneMatrix( FVertexFactoryInput Input )
{
	// Calculated Blend DQ
	float2x4 BlendDQ = CalcBlendDQ(Input);
	NOEXPRESSIONOPTIMIZATIONS
	{
		// Convert to Matrix
		return DQToMatrix(BlendDQ[0], BlendDQ[1]);
	}
}
#endif// TRANSFORM_BLEND
#else
FBoneMatrix CalcBoneMatrix( FVertexFactoryInput Input )
{
	FBoneMatrix BoneMatrix = BoneMatrices[Input.BlendIndex] * ApexDummy;
	return BoneMatrix;
}
#endif // QUAT_SKINNING

/** transform position by weighted sum of skinning matrices */
float3 SkinPosition( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates )
{
#if GPUSKIN_MORPH_BLEND
	float4 Position = float4(MorphPosition(Input, Intermediates),1);
#else
	float4 Position = float4(Input.Position.xyz,1);
#endif
#if TRANSFORM_BLEND
	return CalcPositionFromDualQuat(Intermediates, Position);
#elif QUAT_SKINNING
	return MulBone( Intermediates.BlendMatrix, GetScaledPosition(Intermediates, Position) );
#else
	return MulBone( Intermediates.BlendMatrix, Position );
#endif
}

/** transform the tangent basis vectors */
float3x3 SkinTangents( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates )
{
	float2x3 Tangents;	
#if GPUSKIN_MORPH_BLEND
	// tangent
	float3 TangentX = TangentBias(Input.TangentX);
	// normal
	float4 TangentZ = TangentBias(Input.TangentZ);
	// normal morph offset
	float3 DeltaTangentZ = TangentBias(Input.DeltaTangentZ);	

	// calc new normal by offseting it with the delta
	Tangents[1] = normalize( float3(TangentZ.x,TangentZ.y,TangentZ.z) + DeltaTangentZ );
	// derive the new tangent by orthonormalizing the new normal against
	// the base tangent vector (assuming these are normalized)
	Tangents[0] = normalize( TangentX - (dot(TangentX,Tangents[1]) * Tangents[1]) );
#else
	half4 TangentZ = TangentBias(Input.TangentZ);
	// pass-thru the tangent
	Tangents[0] = TangentBias(Input.TangentX);
	// pass-thru the normal
	Tangents[1] = float3(TangentZ.x,TangentZ.y,TangentZ.z);
#endif

	float3x3 TangentBasis;

#if TRANSFORM_BLEND
	NOEXPRESSIONOPTIMIZATIONS
	{
		TangentBasis[0] = Tangents[0] + 2.0*cross(Intermediates.BlendDQ[0].yzw, cross(Intermediates.BlendDQ[0].yzw, Tangents[0]) + Intermediates.BlendDQ[0].x*Tangents[0]);
		TangentBasis[2] = Tangents[1] + 2.0*cross(Intermediates.BlendDQ[0].yzw, cross(Intermediates.BlendDQ[0].yzw, Tangents[1]) + Intermediates.BlendDQ[0].x*Tangents[1]);
	}
	// derive the new binormal by getting the cross product of the normal and tangent
	// and flip vector based on sign of tangent basis determinant
	TangentBasis[1] = cross(TangentBasis[2], TangentBasis[0]) * TangentZ.w;
#else
	TangentBasis[0] = MulBone( Intermediates.BlendMatrix, float4(Tangents[0],0) );
	TangentBasis[2] = MulBone( Intermediates.BlendMatrix, float4(Tangents[1],0) );
	// derive the new binormal by getting the cross product of the normal and tangent
	// and flip vector based on sign of tangent basis determinant
	TangentBasis[1] = cross(TangentBasis[2], TangentBasis[0]) * TangentZ.w;
#endif

	return TangentBasis;
}

FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
	FVertexFactoryIntermediates Intermediates;
	
	Intermediates.UnpackedPosition = UnpackedPosition(Input);

#if QUAT_SKINNING
	// Get Scale
	Intermediates.BlendScale = 	Input.BlendWeights.x * BoneScales[Input.BlendIndices.x];
	Intermediates.BlendScale += Input.BlendWeights.y * BoneScales[Input.BlendIndices.y];
	Intermediates.BlendScale += Input.BlendWeights.z * BoneScales[Input.BlendIndices.z];
	Intermediates.BlendScale += Input.BlendWeights.w * BoneScales[Input.BlendIndices.w];

	#if TRANSFORM_BLEND
		Intermediates.BlendDQ = CalcBlendDQ(Input);
	#else
		// DQ Note: This does not work with Scale
		Intermediates.BlendMatrix = CalcBoneMatrix( Input );
	#endif
#else
	// DQ Note: This does not work with Scale
	Intermediates.BlendMatrix = CalcBoneMatrix( Input );
#endif


	// Fill TangentBasis
	Intermediates.TangentBasis = SkinTangents(Input, Intermediates);

	return Intermediates;
}

float4 CalcWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return MulMatrix( LocalToWorld, float4(SkinPosition(Input, Intermediates),1) );
}

#if GPUSKIN_DECAL
float2 ComputeDecalTexCoord(float4 Point)
{
	float4 TempPoint = Point-float4(DecalLocation,1);
	float Dot0 = dot( BoneToDecalRow0, TempPoint );
	float Dot1 = dot( BoneToDecalRow1, TempPoint );
	return float2(-Dot0+0.5, -Dot1+0.5);
}
#endif

/**
* Get the 3x3 tangent basis vectors for this vertex factory
*
* @param Input - vertex input stream structure
* @return 3x3 matrix
*/
float3x3 VertexFactoryGetTangentBasis( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return Intermediates.TangentBasis;
}

float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return CalcWorldPosition(Input, Intermediates);
}

FVertexFactoryInterpolants VertexFactoryGetInterpolants(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	FVertexFactoryInterpolants Interpolants;
#if NUM_MATERIAL_TEXCOORDS
#if GPUSKIN_DECAL
	float2 DecalTexCoords = ComputeDecalTexCoord( Input.Position );
	for(int CoordIndex = 0;CoordIndex < NUM_MATERIAL_TEXCOORDS;CoordIndex++)
		Interpolants.TexCoords[CoordIndex] = DecalTexCoords;
#else
	for(int CoordIndex = 0;CoordIndex < NUM_MATERIAL_TEXCOORDS;CoordIndex++)
		Interpolants.TexCoords[CoordIndex] = Input.TexCoords[CoordIndex];
#endif
#endif

#if WORLD_COORDS
	float3x3 TangentBasis = Intermediates.TangentBasis;
	#if SM2_PROFILE
		// In SM2 these vectors will store the tangent basis. PackColorN scales them to [0..1] to fit in the COLOR0 and COLOR1 interpolators.
		// We cannot combine the tangent to local and local to world due to insufficient temporary registers in this vertex factory
		half4 TangentZ = TangentBias(Input.TangentZ);
		Interpolants.TangentToWorld0 = TangentBasis[0];
		Interpolants.TangentToWorld2 = float4(TangentBasis[2], TangentZ.w);
	#else
		// The inverse of LocalToTangent is just the transpose because it is an orthonormal basis
		float3x3 TangentToLocal = transpose(TangentBasis);

		// Calculate the transform from tangent to world space
		#if PS3
			float3x3 TangentToWorld = mul(transpose((float3x3)LocalToWorld), TangentToLocal); 
		#else
			float3x3 TangentToWorld = mul((float3x3)LocalToWorld, TangentToLocal); 
		#endif

		Interpolants.TangentToWorld0 = TangentToWorld[0];
		Interpolants.TangentToWorld2 = float4(TangentToWorld[2], TangentBias(Input.TangentZ.w) * LocalToWorldRotDeterminantFlip);
	#endif
	Interpolants.TangentToWorld0 = PackColor3( Interpolants.TangentToWorld0 );
	Interpolants.TangentToWorld2 = PackColor4( Interpolants.TangentToWorld2 );
#endif

#if !COMPILER_SUPPORTS_EMPTY_STRUCTS && !WORLD_COORDS && !NUM_MATERIAL_TEXCOORDS
	Interpolants.Dummy = float4(0,0,0,0);
#endif

	return Interpolants;
}

/** for motion blur */
float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
#if GPUSKIN_DECAL
	return MulMatrix( LocalToWorld, float4(SkinPosition(Input, Intermediates),1) );
#else
	return MulMatrix( PreviousLocalToWorld, float4(SkinPosition(Input, Intermediates),1) );
#endif
}

/**
* Transform a vector from world space to tangent space
*
* @param Input - vertex input stream structure
* @param TangentBasis - 3x3 matrix to transform to tangent space
* @param WorldVector - vector in world space to transform 
* @return vector in tangent space
*/
float3 VertexFactoryWorldToTangentSpace( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates, float3x3 TangentBasis, float3 WorldVector )
{
	// we use a straight mul here because we are generating the matrix, so we don't worry about column major vs row major (which is what MulMatrix manages per-platform)
	return mul(TangentBasis, MulMatrix((float3x3)WorldToLocal, WorldVector) );
}

float3 VertexFactoryGetWorldNormal(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	float3x3 TangentBasis = VertexFactoryGetTangentBasis(Input, Intermediates);
	float4 WorldNormal = MulMatrix( LocalToWorld, float4(TangentBasis[2],0) );
	return WorldNormal.xyz;
}
