#include "stdafx.h"
#include <assert.h>												// assert()
#include <float.h>												// FLT_MAX
#include "PbTri.h"												// CPbTri
#include "PolyBump.h"											// CPbMesh
#include "SimpleTriangleRasterizer.h"			// CSimpleTriangleRasterizer
#include "intersect_triangle.h"						// intersect_triangle()
#include "Cry_Math.h"											// Vec3



// extend bounding box
void CPbTri::ExtendMinMax( Vec3 &inoutMin, Vec3 &inoutMax )
{
	assert(this);

	for(int i=0;i<3;i++)
	{
		inoutMin.x=min(inoutMin.x,m_Verts[i].x);inoutMax.x=max(inoutMax.x,m_Verts[i].x);
		inoutMin.y=min(inoutMin.y,m_Verts[i].y);inoutMax.y=max(inoutMax.y,m_Verts[i].y);
		inoutMin.z=min(inoutMin.z,m_Verts[i].z);inoutMax.z=max(inoutMax.z,m_Verts[i].z);
	}
}


void CPbTri::ExtendSphere( Vec3 invMid, float &outfRadius2 )
{
	assert(this);

	float len2;

	len2=(invMid-m_Verts[0]).len2();	if(outfRadius2<len2)outfRadius2=len2;
	len2=(invMid-m_Verts[1]).len2();	if(outfRadius2<len2)outfRadius2=len2;
	len2=(invMid-m_Verts[2]).len2();	if(outfRadius2<len2)outfRadius2=len2;
}


// returnwert:0=left,1=both,2=right
DWORD CPbTri::PlaneSplit( int iniAxis, float value )
{
	assert(iniAxis==0 || iniAxis==1 || iniAxis==2);		// x,y or z

	// left side
	if(m_Verts[0][iniAxis]>value && m_Verts[1][iniAxis]>value && m_Verts[1][iniAxis]>value)return(0);
	// right side
	if(m_Verts[0][iniAxis]<value && m_Verts[1][iniAxis]<value && m_Verts[1][iniAxis]<value)return(2);
	// left and right side
	return(1);
}



// does the triangles share one or more vertices?
bool CPbTri::IsConnectedTo( CPbTri& inTriangle )
{
	if(this==&inTriangle)return(true);

	for(int i=0;i<3;i++)
	{
		for(int e=0;e<3;e++)
		{
			if(inTriangle.m_Verts[i].x==inTriangle.m_Verts[e].x)
			if(inTriangle.m_Verts[i].y==inTriangle.m_Verts[e].y)
			if(inTriangle.m_Verts[i].z==inTriangle.m_Verts[e].z)
				return(true);
		}
	}
	return(false);
}



//draw triangle in software
//////////////////////////////////////////////////////////////////////
void CPbTri::DrawTriPointer( int color, const bool inbConservative )
{ 
	float fU[3],fV[3];

	for(int i=0;i<3;i++)
	{
		fU[i]=(float)(m_fS[i]*(float)CPbMesh::m_nBumpImageSizeX);
		fV[i]=(float)(m_fT[i]*(float)CPbMesh::m_nBumpImageSizeY);
	}

	CSimpleTriangleRasterizer Raster(CPbMesh::m_nBumpImageSizeX,CPbMesh::m_nBumpImageSizeY);

	Raster.DWORDFlatFill((DWORD *)CPbMesh::m_pnTriPointer,CPbMesh::m_nBumpImageSizeX,fU,fV,(DWORD)color,inbConservative);
}


//calc the area of a triangle using texture coords
//////////////////////////////////////////////////////////////////////
float CPbTri::CalcArea( float *pfX, float *pfY )
{
//m_fArea=(bx*cy - by*cx) + (ax*by - ay*bx) + (ay*cx - ax*cy) 

  float prod1=pfX[1]*pfY[2]-pfY[1]*pfX[2];
  float prod2=pfX[0]*pfY[1]-pfY[0]*pfX[1];
  float prod3=pfY[0]*pfX[2]-pfX[0]*pfY[2];

  return (prod1+prod2+prod3)/2.0f; 
}

//calc the area of a triangle in 3d
//////////////////////////////////////////////////////////////////////
float CPbTri::CalcTriArea( const Vec3 &p0, const Vec3 &p1, const Vec3 &p2 )
{
/*
  Vec3 verts[3];
  verts[0]=p0;
  verts[1]=p1;
  verts[2]=p2;

	//  return (::CalcArea(verts,3,normal));
*/

	// http://www.flipcode.com/geometry/issue10.shtml
	// ABC = (1/2)*||(B–A)×(C–A)||

	return( ((p1-p0)^(p2-p0)).len() *0.5f );
}



//get the interpolated normal using barycentric coords (for the high poly mesh)
//////////////////////////////////////////////////////////////////////
void CPbTri::CalcBarycentricCoordsFromPoint( const Vec3 &point, Vec3 &outvNormal, bool inbDoClipping )
{
  // first sub tri
  float a1=CalcTriArea(point,m_Verts[1],m_Verts[2]);
  // second sub tri  
  float a2=CalcTriArea(m_Verts[0],point,m_Verts[2]);

	// to prevent crash
	if(m_fArea3d==0.0f){ outvNormal=m_Plane.n;return; }

  // calc alpha beta and gamma components as area ratio
  float alpha=a1/m_fArea3d;
  float beta=a2/m_fArea3d;
	
	if(inbDoClipping)	// clip the barycentric coordinates inside the traingle
	{
		float sub=alpha+beta-1.0f;
		if(sub>0.0f)
		{
			sub*=0.5f;
			alpha-=sub;
			beta-=sub;
		}
		if(alpha<0.0f)alpha=0.0f;
		if(beta<0.0f)beta=0.0f;
	}

  // since the sum is always 1 'cause the point is inside the polygon,
  // gamma can be calculated in this way...
  float gamma=1.0f-alpha-beta;

  // interpolate normal vector
  outvNormal= alpha*m_VertsNormal[0] + beta*m_VertsNormal[1] + gamma*m_VertsNormal[2];
	outvNormal.Normalize();		// m.m. for better results
}



//calc the barycentric coords from texture coords (in the low poly model)
// normalize normal after calling this function
//////////////////////////////////////////////////////////////////////
void CPbTri::CalcBarycentricCoordsFromUV( float s, float t, Vec3 &point, Vec3 &normal,
																				  Vec3 &outvTangent, Vec3 &outvBinormal, Vec3 &outvTNormal, 
																					bool inbDoClipping )
{
	float fBar[3];		// barycentric coordiantes

	CalcBarycentrics(s,t,fBar,inbDoClipping);

  //interpolate position and normal
  point=  fBar[0]*m_Verts[0]				+ fBar[1]*m_Verts[1]				+ fBar[2]*m_Verts[2];
  normal= fBar[0]*m_VertsNormal[0]	+ fBar[1]*m_VertsNormal[1]	+ fBar[2]*m_VertsNormal[2];

	// normal is not normalized because we need it's length for renormalizing in tangentspace

	// for tangent space bumpmapping
  outvTangent=(fBar[0]*m_vTangent[0])+(fBar[1]*m_vTangent[1])+(fBar[2]*m_vTangent[2]);
	outvTangent.Normalize();

	// for tangent space bumpmapping
  outvBinormal=(fBar[0]*m_vBinormal[0])+(fBar[1]*m_vBinormal[1])+(fBar[2]*m_vBinormal[2]);
	outvBinormal.Normalize();

  outvTNormal=(fBar[0]*m_vTNormal[0])+(fBar[1]*m_vTNormal[1])+(fBar[2]*m_vTNormal[2]);
	outvTNormal.Normalize();
}


// helper method
void CPbTri::CalcBarycentrics( float inS, float inT, float outCoor[3], bool inbDoClipping )
{
	float ftemptriX[3],ftemptriY[3];  

  //calc the area of first sub triangle
  memcpy(ftemptriX,m_fS,sizeof(float)*3);
  memcpy(ftemptriY,m_fT,sizeof(float)*3);

  ftemptriX[0]=inS;ftemptriY[0]=inT;
	float a1=CalcArea(ftemptriX,ftemptriY);

  //calc the area of second sub triangle
  memcpy(ftemptriX,m_fS,sizeof(float)*3);
  memcpy(ftemptriY,m_fT,sizeof(float)*3);

  ftemptriX[1]=inS;ftemptriY[1]=inT;
  float a2=CalcArea(ftemptriX,ftemptriY);  

	//calc alpha beta and gamma components as area ratio
  float alpha=a1/m_fArea;
  float beta=a2/m_fArea;
	
	if(inbDoClipping)
	{
		float sub=alpha+beta-1.0f;
		if(sub>0.0f)
		{
			sub*=0.5f;
			alpha-=sub;
			beta-=sub;
		}
		if(alpha<0.0f)alpha=0.0f;
		if(beta<0.0f)beta=0.0f;
	}

	outCoor[0]=alpha;
	outCoor[1]=beta;
  outCoor[2]=1.0f-alpha-beta;
}


bool CPbTri::CalcIntersectionFromTo( const Vec3 &invStart, const Vec3 &invEnd ) 
{
	Vec3 vDir=invEnd-invStart;			// vDir.Normalize(); // Baustelle???

	double orig[3]={invStart.x,invStart.y,invStart.z};
	double dir[3]={ vDir.x,vDir.y,vDir.z };
	double vert1[3]={ m_Verts[0].x, m_Verts[0].y, m_Verts[0].z };
	double vert2[3]={ m_Verts[1].x, m_Verts[1].y, m_Verts[1].z };
	double vert3[3]={ m_Verts[2].x, m_Verts[2].y, m_Verts[2].z };
	double t,u,v;

	return(intersect_triangle(orig,dir,vert1,vert2,vert3,&t,&u,&v));
}




float CPbTri::CalcIntersectionFromDir( const Vec3 &invStart, const Vec3 &invDir ) 
{
	Vec3 vDir=invDir;		vDir.Normalize(); // Baustelle???

	double orig[3]={invStart.x,invStart.y,invStart.z};
	double dir[3]={ vDir.x,vDir.y,vDir.z };
	double vert1[3]={ m_Verts[0].x, m_Verts[0].y, m_Verts[0].z };
	double vert2[3]={ m_Verts[1].x, m_Verts[1].y, m_Verts[1].z };
	double vert3[3]={ m_Verts[2].x, m_Verts[2].y, m_Verts[2].z };
	double t,u,v;

	if(!intersect_triangle(orig,dir,vert1,vert2,vert3,&t,&u,&v))
		return(FLT_MAX);

	return((float)t);
}












// based on "Curved PN Triangles paper"
// return the Trueform/PN Triangle generated position (used for displacement mapping)
void CPbTri::CalcPNTriangleData( float inS, float inT, bool inbDoClipping, Vec3 &outPos, Vec3 &outNormal, const bool inbQuadratic  )
{
	float _fBar[3];		// barycentric coordiantes
	float fBar[3];		// barycentric coordiantes

	CalcBarycentrics(inS,inT,_fBar,inbDoClipping);

	fBar[0]=_fBar[1];
	fBar[1]=_fBar[2];
	fBar[2]=_fBar[0];

	Vec3 b300=m_Verts[0],b030=m_Verts[1],b003=m_Verts[2];
	Vec3 n200=m_VertsNormal[0],n020=m_VertsNormal[1],n002=m_VertsNormal[2];

	Vec3 p12=b030-b300;
	Vec3 p23=b003-b030;
	Vec3 p31=b300-b003;

	float v12= 2*(p12*(n200+n020)) / (p12*p12);
	float v23= 2*(p23*(n002+n020)) / (p23*p23);
	float v31= 2*(p31*(n200+n002)) / (p31*p31);

	Vec3 n110=n200+n020-v12*(b030-b300);
	Vec3 n011=n020+n002-v23*(b003-b030);
	Vec3 n101=n002+n200-v31*(b300-b003);

	n110.Normalize();
	n011.Normalize();
	n101.Normalize();
	
	float w12=(b030-b300)*n200;
	float w21=(b300-b030)*n020;
	float w23=(b003-b030)*n020;
	float w32=(b030-b003)*n002;
	float w31=(b300-b003)*n002;
	float w13=(b003-b300)*n200;

	Vec3 b210=(2*b300+b030-w12*n200)/3.0f;
	Vec3 b120=(2*b030+b300-w21*n020)/3.0f;
	Vec3 b021=(2*b030+b003-w23*n020)/3.0f;
	Vec3 b012=(2*b003+b030-w32*n002)/3.0f;
	Vec3 b102=(2*b003+b300-w31*n002)/3.0f;
	Vec3 b201=(2*b300+b003-w13*n200)/3.0f;

	Vec3 E=(b210+b120+b021+b012+b102+b210)/6;
	Vec3 V=(b300+b030+b003)/3;
	Vec3 b111=E+(E-V)/2;

	outPos = b300*(fBar[2]*fBar[2]*fBar[2]) + b030*(fBar[0]*fBar[0]*fBar[0]) + b003*(fBar[1]*fBar[1]*fBar[1])
				 + 3*b210*(fBar[2]*fBar[2]*fBar[0]) + 3*b120*(fBar[0]*fBar[0]*fBar[2]) + 3*b201*(fBar[2]*fBar[2]*fBar[1])
				 + 3*b021*(fBar[0]*fBar[0]*fBar[1]) + 3*b102*(fBar[1]*fBar[1]*fBar[2]) + 3*b012*(fBar[1]*fBar[1]*fBar[0])
				 + 6*b111*(fBar[0]*fBar[1]*fBar[2]);

	if(inbQuadratic)
	{
		outNormal = n200*(fBar[2]*fBar[2]) + n020*(fBar[0]*fBar[0]) + n002*(fBar[1]*fBar[1])
							+ n110*(fBar[2]*fBar[0]) + n011*(fBar[0]*fBar[1]) + n101*(fBar[2]*fBar[1]);
	}
	else
	{
		outNormal = m_VertsNormal[0]*_fBar[0] + m_VertsNormal[1]*_fBar[1] + m_VertsNormal[2]*_fBar[2];
	}
}


// calculates m_Plane,m_fArea3d and m_fArea
void CPbTri::RefreshInternals( void )
{
	m_Plane.CalcPlane(m_Verts[2],m_Verts[1],m_Verts[0]);					// triangle plane
	m_fArea3d=CalcTriArea(m_Verts[0],m_Verts[1],m_Verts[2]);				// area size in world space
	m_fArea=CalcArea(m_fS,m_fT);																	// area size in texture
	if(m_fArea==0.0f)m_fArea=0.001f;															// to prevent crash
}
