///////////////////////////////////////////////////////////////////////////////
/// @file LathedBrush.cpp
///
/// @brief Creation of a lathed brush
///
/// @author Assassin 
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Feb 2002
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deBrush_priv.hpp"
#include "deStack.hpp"
//#include <cstdarg>
#include "deMath.hpp"
//#include "octree.hpp"

WorldObjectClassImp(deLathedBrush);

static const deBrush::DataQuantity_t s_LathedQuantities = 
{
	deBrush::DataQuantity_t::Quantity_Constant, 
	2,	//CVNum
	4,	//NumFaces
	4	//CoordsPerFace
};

deLathedBrush::deLathedBrush()
:deBrush(Brush_Lathed, s_LathedQuantities)
{
	m_Line = NULL;
	m_Degree = 360;
	m_LOD = 4;
	m_LOD2 = 4;
	m_Style = LatheSurface;
}

deLathedBrush::~deLathedBrush()
{
	m_Line->Release();
}

void deLathedBrush::SetLine(IdeLine * line)
{
	if (m_Line)
		m_Line->Release();
	m_Line = line->Copy();
	m_Changed = deTRUE;
}

IdeLine * deLathedBrush::GetLine()
{
	return m_Line;
}

void deLathedBrush::UpdateBrush()
{
	Lathe();
}

void deLathedBrush::SetStyle(LatheStyle style)
{
	m_Style = style;
}

deLathedBrush::LatheStyle deLathedBrush::GetStyle()
{
	return m_Style;
}

void deLathedBrush::SetDegree(deDouble RotDegree)
{
	m_Degree = RotDegree;
}

deDouble deLathedBrush::GetDegree()
{
	return m_Degree;
}

void deLathedBrush::Lathe()
{
	m_PolyList.EmptyList();
//	m_Octree->Clear();
	// we need 2 control verts for the rotation axis, and we need a line to lathe
	if (m_CVNum < 2 || m_Line == NULL)
		return;
	if (m_LOD < 3 || m_LOD2 < 1)
		return;

	int i,j,k, Length = m_Line->GetLength();
	deDouble f, Radius = -1, OneOverLOD2 = (deDouble)(1.0 / m_LOD2);
	deDouble PercentX, PercentY;
	deVec3d LinePoint;
	deTexCoord FaceCoords[4][4];
	bool NeedCap0 = false, NeedCap1 = false;
	int NumPoints = Length * m_LOD2 + 1;
	LatheStyle UseStyle = m_Style;
	deBPoly p;

	// a bunch of values that we will be using to construct our polygons from
	// one iteration at a time
	deVec3d *Points0, *Points1;
	deVec3d *Norms0, *Norms1;
	deTexCoord *SideCoords0, *SideCoords1;
	Points0 = new deVec3d[NumPoints];
	Points1 = new deVec3d[NumPoints];
	Norms0 = new deVec3d[NumPoints];
	Norms1 = new deVec3d[NumPoints];
	SideCoords0 = new deTexCoord[NumPoints];
	SideCoords1 = new deTexCoord[NumPoints];

	// copy the values into a local array
	for (i = 0; i < 4; i++)
	{
		FaceCoords[0][i] = m_FaceList[0][i];
		FaceCoords[3][i] = m_FaceList[3][i];
	}
	for (i = 0; i < 3; i++)
	{
		FaceCoords[1][i] = m_FaceList[1][i];
		FaceCoords[2][i] = m_FaceList[2][i];
	}

	// set up the matrix that will rotate the vertices each iteration
	deTransform x, xminus;
	deTransform rot;
	{
		deVec3d L,U,I = {0,0,1};
		U = m_CVList[1] - m_CVList[0];
		Vec3d_Normalize(U);
		if (Vec3d_Compare(U,I))
		{
			I.Y = -1; I.Z = 0;
		}
		L = Vec3d_Cross(U,I);
		Vec3d_Normalize(L);
		Vec3d_Normalize(U);
		x.SetRightUpIn(L,U,I);
		x.SetTranslation(m_CVList[0]);

		x.GetTranspose(xminus);

		rot.SetYRot(DEG2RAD(m_Degree/(m_LOD)));
	}

	k = 0;
	f = 0;
	deVec3d cross,axis,norm,normal;
	{
		axis = m_CVList[1];
		axis -= m_CVList[0];
		Vec3d_Normalize(axis);
		deDouble InterpPoint = 0;
		deDouble projection;
		do
		{
			m_Line->GetPointOnLine(InterpPoint, normal);
			normal -= m_CVList[0];
			projection = Vec3d_Dot(axis, normal);
			InterpPoint += 0.1;
			if (InterpPoint >= 1.0)
			{
				DEBUGMSG0("deLathedBrush::Lathe - unable to generate a reference normal");
				break;
			}
		}
		while (Compare_Epsilon(projection,Vec3d_Length(normal)));
		LinePoint = axis;
		LinePoint *= projection;
		normal -= LinePoint;
		Vec3d_Normalize(normal);
	}
	for (j = 0; j < Length; j++)
	{	// iterate through each constituent line (groups only)
		int NumPoints = (j+1)*m_LOD2+1;
		while (k < NumPoints)
		{	// sample the line at various places
			m_Line->GetPointOnLine(f+j, LinePoint);
			//Distance = Dist(m_CVList[0], m_CVList[1], LinePoint);
			Points0[k] = LinePoint;
			m_Line->GetTangentAtPoint(f+j,LinePoint);
			cross = Vec3d_Cross(LinePoint,axis);
			if (Vec3d_Compare(cross, deZeroVec))
			{
				cross = Vec3d_Cross(LinePoint, normal);
			}
			deDouble value = Vec3d_Dot(LinePoint, normal);
			if (value > 0)
				norm = Vec3d_Cross(LinePoint, cross);
			else
				norm = Vec3d_Cross(cross, LinePoint);
			Vec3d_Normalize(norm);
			Norms0[k] = norm;
			k++;
			f += OneOverLOD2;
			if (f > 1)
				f = 1;
		}
		f = OneOverLOD2;
	}
	deVec3d EndP[2] = {{{0}}};
	// make the thing solid
	if (m_Style == LatheCap)
	{	// if we have a cap, let's put it on
		xminus.TransformArray(Points0, Points0, NumPoints);
		EndP[0].Y = Points0[0].Y;
		EndP[1].Y = Points0[NumPoints-1].Y;
		if (EndP[0].Y == EndP[1].Y)
			UseStyle = LatheSurface;
		x.Transform(EndP[0], EndP[0]);
		x.Transform(EndP[1], EndP[1]);
		x.TransformArray(Points0, Points0, NumPoints);
	}
	else if (m_Style == LatheCylinder)
	{	// if it's lathed into a cylinder, make the cylinder
		xminus.TransformArray(Points0, Points0, NumPoints);
		deDouble Distance = Dist(m_CVList[0], m_CVList[1], Points0[0]);
		deDouble D2;
		NeedCap0 = false;
		NeedCap1 = true;
		for (i = 1; i < NumPoints; i++)
		{
			D2 = Dist(m_CVList[0], m_CVList[1], Points0[i]);
			if (D2 > Distance)
			{
				Distance = D2;
				NeedCap0 = true;
				if (i == NumPoints-1)
					NeedCap1 = false;
			}
		}
		Radius = Distance;
		EndP[0].Y = Points0[0].Y;
		EndP[1].Y = Points0[NumPoints-1].Y;
		x.Transform(EndP[0], EndP[0]);
		x.Transform(EndP[1], EndP[1]);
		x.TransformArray(Points0, Points0, NumPoints);
	}
	int CountCoord;
	for (CountCoord = 0; CountCoord < NumPoints; CountCoord++)
	{
		PercentY = ((deDouble)CountCoord)/(NumPoints-1);
		SideCoords0[CountCoord] = (FaceCoords[0][2] - FaceCoords[0][0])*((deFloat)PercentY) + FaceCoords[0][0];
	}
	for (i = 0; i < m_LOD; i++)
	{	// for each face of the lathed surface, we rotate a little
		xminus.TransformArray(Points0, Points0, NumPoints);
		xminus.TransformArray(Norms0, Norms0, NumPoints);
		rot.TransformArray(Points0, Points1, NumPoints);
		rot.TransformArray(Norms0, Norms1, NumPoints);
		x.TransformArray(Points0, Points0, NumPoints);
		x.TransformArray(Points1, Points1, NumPoints);
		x.TransformArray(Norms0, Norms0, NumPoints);
		x.TransformArray(Norms1, Norms1, NumPoints);
		for (CountCoord = 0; CountCoord < NumPoints; CountCoord++)
		{
			PercentY = ((deDouble)CountCoord)/(NumPoints-1);
			PercentX = ((deDouble)i+1)/(m_LOD);
			SideCoords1[CountCoord] = ((FaceCoords[0][2] - FaceCoords[0][0])*((deFloat)PercentY) + FaceCoords[0][0])*((deFloat)(1-PercentX));
			SideCoords1[CountCoord] = SideCoords1[CountCoord] + ((FaceCoords[0][3] - FaceCoords[0][1])*((deFloat)PercentY) + FaceCoords[0][1])*((deFloat)PercentX);
		}
		for (j = 0; j < NumPoints-1; j++)
		{
			MakeTri(p, Points0[j], Points0[j+1], Points1[j],
					Norms0[j], Norms0[j+1], Norms1[j],
					SideCoords0[j], SideCoords0[j+1], SideCoords1[j]);
			m_PolyList.AddElement(p);
//			m_Octree->AddPoly(p);
			MakeTri(p, Points1[j+1], Points1[j], Points0[j+1],
					Norms1[j+1], Norms1[j], Norms0[j+1],
					SideCoords1[j+1], SideCoords1[j], SideCoords0[j+1]);
			m_PolyList.AddElement(p);
//			m_Octree->AddPoly(p);
		}
		if (UseStyle == LatheCap)
		{
			MakeTri(p, EndP[0], Points0[0], Points1[0]);
			m_PolyList.AddElement(p);
//			m_Octree->AddPoly(p);
			MakeTri(p, EndP[1], Points0[NumPoints-1], Points1[NumPoints-1]);
			m_PolyList.AddElement(p);
//			m_Octree->AddPoly(p);
		}
		else if (UseStyle == LatheCylinder)
		{
			// what an evil identifier!
			// It's just a quad with the 4 vertices in it
			deVec3d _[2][2];
			_[0][0] = Points0[0] - EndP[0];
			_[0][1] = Points1[0] - EndP[0];
			_[1][0] = Points0[NumPoints-1] - EndP[1];
			_[1][1] = Points1[NumPoints-1] - EndP[1];
			Vec3d_Normalize(_[0][0]);
			Vec3d_Normalize(_[0][1]);
			Vec3d_Normalize(_[1][0]);
			Vec3d_Normalize(_[1][1]);
			_[0][0] = _[0][0] * Radius + EndP[0];
			_[0][1] = _[0][1] * Radius + EndP[0];
			_[1][0] = _[1][0] * Radius + EndP[1];
			_[1][1] = _[1][1] * Radius + EndP[1];
			if (NeedCap0)
			{
				MakeTri(p, _[0][0], _[0][1], Points0[0]);
				m_PolyList.AddElement(p);
//				m_Octree->AddPoly(p);
				MakeTri(p, Points0[0], _[0][1], Points1[0]);
				m_PolyList.AddElement(p);
//				m_Octree->AddPoly(p);
			}
			if (NeedCap1)
			{
				MakeTri(p, _[1][0], _[1][1], Points0[NumPoints-1]);
				m_PolyList.AddElement(p);
//				m_Octree->AddPoly(p);
				MakeTri(p, Points0[0], _[1][1], Points1[NumPoints-1]);
				m_PolyList.AddElement(p);
//				m_Octree->AddPoly(p);
			}
			MakeTri(p, _[0][1], _[0][0], _[1][0]);
			m_PolyList.AddElement(p);
//			m_Octree->AddPoly(p);
			MakeTri(p, _[1][0], _[1][1], _[0][1]);
			m_PolyList.AddElement(p);			
//			m_Octree->AddPoly(p);
		}
		{
		void * p;
		p = Points0;
		Points0 = Points1;
		Points1 = (deVec3d*)p;
		p = Norms0;
		Norms0 = Norms1;
		Norms1 = (deVec3d*)p;
		p = SideCoords0;
		SideCoords0 = SideCoords1;
		SideCoords1 = (deTexCoord*)p;
		}
	}

	delete[] Points0;
	delete[] Points1;
	delete[] Norms0;
	delete[] Norms1;
	delete[] SideCoords0;
	delete[] SideCoords1;

//	m_Octree->Generate();
}

deDouble deLathedBrush::Dist(deVec3d LineP1, deVec3d LineP2, deVec3d Pt)
{
	deVec3d LineVec = (LineP1 - LineP2), tempvec;
	Vec3d_Normalize(LineVec);
	tempvec = LineP1 - Pt;
	return Vec3d_Length(Vec3d_Cross(LineVec, tempvec));
}

deBrush * deLathedBrush::CopyBrush()
{
	deLathedBrush * b = NULL;
	b = new deLathedBrush;
	b->m_Line = m_Line;
	b->m_Style = m_Style;
	b->m_Degree = m_Degree;
	return b;
}

deBoolean deLathedBrush::Serialize(IdeFile* FS)
{
	if (!deBrush::Serialize(FS))
		return deFALSE;
	return deTRUE;
}

deBoolean deLathedBrush::DeSerialize(IdeFile* FS, long DataLength)
{
	if (!deBrush::DeSerialize(FS, DataLength))
		return deFALSE;
	return deTRUE;
}

