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

#define MAX_BONES 75

#ifndef GPUSKIN_MORPH_BLEND
#define GPUSKIN_MORPH_BLEND 0
#endif

#ifndef GPUSKIN_DECAL
#define GPUSKIN_DECAL 0
#endif

float4x4 LocalToWorld;
#if GPUSKIN_DECAL
float3x3 WorldToLocal;
float4 BoneToDecalRow0;
float4 BoneToDecalRow1;
float3 DecalLocation;
#else
float4x4 PreviousLocalToWorld;
float3x3 WorldToLocal;
#endif


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
	uint4	BlendIndices	: BLENDINDICES0;
#else
	int4	BlendIndices	: BLENDINDICES0;
#endif
	float4	BlendWeights	: BLENDWEIGHT0;	
#if NUM_MATERIAL_TEXCOORDS
	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
	// xyz=normal w=determinant
	float4	TangentBasisNormal	: COLOR0;
	float3	TangentBasisTangent	: COLOR1;
#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
};

FMaterialParameters GetMaterialParameters(FVertexFactoryInterpolants Interpolants)
{
	FMaterialParameters	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
	Result.TangentBasisInverse = CalcInvTangentBasis(Interpolants.TangentBasisNormal,Interpolants.TangentBasisTangent);
#endif
	return Result;
}

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


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

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

/** skinning matrices stored in 3x4 order */
FBoneMatrix BoneMatrices[MAX_BONES];

#if SM4_PROFILE
}; // VSBoneConstants
#endif

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

/** 
 * Adds the delta TangentZ from the combined morph targets to the vertex TangentZ 
 * and then derives the new TangentX,TangentY 
 */
float3x3 MorphTangents( FVertexFactoryInput Input )
{
	float3x3 Result=0;

	// 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
	Result[2] = 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)
	Result[0] = normalize( TangentX - (dot(TangentX,Result[2]) * Result[2]) );

	// derive the new binormal by getting the cross product of the normal and tangent
	// and flip vector based on sign of tangent basis determinant
	Result[1] = normalize( cross(Result[2], Result[0]) * TangentZ.w );

	return Result;
}
#endif

FBoneMatrix CalcBoneMatrix( FVertexFactoryInput Input )
{
	FBoneMatrix BoneMatrix = Input.BlendWeights.x * BoneMatrices[Input.BlendIndices.x];
	BoneMatrix += Input.BlendWeights.y * BoneMatrices[Input.BlendIndices.y];
	BoneMatrix += Input.BlendWeights.z * BoneMatrices[Input.BlendIndices.z];
	BoneMatrix += Input.BlendWeights.w * BoneMatrices[Input.BlendIndices.w];
	return BoneMatrix;
}

/** transform position by weighted sum of skinning matrices */
float3 SkinPosition( FVertexFactoryInput Input )
{
#if GPUSKIN_MORPH_BLEND
	float4 Position = float4(MorphPosition(Input),1);
#else
	float4 Position = float4(Input.Position.xyz,1);
#endif

	FBoneMatrix BoneMatrix = CalcBoneMatrix( Input );
	return MulBone( BoneMatrix, Position );
}

/** transform the tangent basis vectors */
float3x3 SkinTangents( FVertexFactoryInput Input )
{
#if GPUSKIN_MORPH_BLEND
	float3x3 Tangents = MorphTangents(Input);
#else
	float3x3 Tangents;	
	half4 TangentZ = TangentBias(Input.TangentZ);
	// pass-thru the tangent
	Tangents[0] = TangentBias(Input.TangentX);
	// pass-thru the normal
	Tangents[2] = float3(TangentZ.x,TangentZ.y,TangentZ.z);
	// derive the binormal by getting the cross product of the normal and tangent
	Tangents[1] = cross(Tangents[2], Tangents[0]) * TangentZ.w;
#endif

	float3x3 TangentBasis;
	FBoneMatrix BoneMatrix = CalcBoneMatrix( Input );
	TangentBasis[0] = MulBone( BoneMatrix, float4(Tangents[0],0) );
	TangentBasis[1] = MulBone( BoneMatrix, float4(Tangents[1],0) );
	TangentBasis[2] = MulBone( BoneMatrix, float4(Tangents[2],0) );
	return TangentBasis;
}

float4 CalcWorldPosition(FVertexFactoryInput Input)
{
	// Ensure that the position transforming code is optimized the same way regardless of what shader it's being compiled for.
	// This avoids slightly different FP precision for the position used by different shaders, which causes z fighting between different passes of the same mesh.
	float4 Result;
	ISOLATE
	{
		Result = MulMatrix( LocalToWorld, float4(SkinPosition(Input),1) );
	}
	return Result;
}

#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

float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input,out 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 = SkinTangents(Input);
	half4 TangentZ = TangentBias(Input.TangentZ);
	Interpolants.TangentBasisNormal = float4(TangentBasis[2],TangentZ.w) * 0.5 + 0.5;
	Interpolants.TangentBasisTangent = TangentBasis[0] * 0.5 + 0.5;
#endif

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

	return CalcWorldPosition(Input);
}

/** for depth-only pass */
float4 VertexFactoryGetWorldPositionOnly(FVertexFactoryInput Input)
{
	return CalcWorldPosition(Input);
}

/** for motion blur */
float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input,out 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 = SkinTangents(Input);
	half4 TangentZ = TangentBias(Input.TangentZ);
	Interpolants.TangentBasisNormal = float4(TangentBasis[2],TangentZ.w) * 0.5 + 0.5;
	Interpolants.TangentBasisTangent = TangentBasis[0] * 0.5 + 0.5;	
#endif

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

#if GPUSKIN_DECAL
	return MulMatrix( LocalToWorld, float4(SkinPosition(Input),1) );
#else
	return MulMatrix( PreviousLocalToWorld, float4(SkinPosition(Input),1) );
#endif
}

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

/**
* 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, 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(WorldToLocal, WorldVector) );
}

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