/**
* Path Segment
* @version: 01-29-2005
* @author Joseph 'Pcjoe' Florencio
*/

#include "fxsys.h"

#ifdef CLIENT_DLL
#include "../hud.h"
#include "../cl_util.h"
#include "../studio_util.h"
#endif

/******
* Path Segment
*******/
// Constructor
CPathSegment::CPathSegment()
{	setFXEvent(NULL);
}

/******
* Linear Path
*******/
// Constructor
CLinearPath::CLinearPath()
{
}

// Get Length
float CLinearPath::getLength( void )
{	float flDist=0;
	std::list<CFXDataPoint>::iterator iterCurrent;
	std::list<CFXDataPoint>::iterator iterNext;
	// Iterate through all points and calculate total distance
	ForEachIn(iterCurrent, m_Points)
	{	iterNext = iterCurrent;
		iterNext++;
		if(iterNext != m_Points.end())
			flDist += (iterCurrent->getPosition()-iterNext->getPosition()).Length();
		else
			break;
	}
	return flDist;
}

// Add Static Point
void CLinearPath::addPoint( vec3_t v )
{	CFXDataPoint dp(v);
	addPoint(dp);
}

// Add Data Point
void CLinearPath::addPoint( CFXDataPoint dp )
{	dp.setFXEvent(getFXEvent());
	m_Points.push_back(dp);
}

// Add Dynamic Bone Point
void CLinearPath::addPoint( std::string bone )
{	CFXDataPoint dp(bone);
	addPoint(dp);
}

// Add Camera Point
void CLinearPath::addCameraPoint( void )
{	CFXDataPoint dp;
	dp.setDynamicCamera();
	addPoint(dp);
}

// Change this linear path to a bezier path
CBezierPath *CLinearPath::toBezierPath( void )
{	// Create new bezier path
	CBezierPath *pBezierPath = new CBezierPath;

	// Iterate through all bezier points and convert to linear
	std::list<CFXDataPoint>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_Points)
	{	// Create curveto point (temp data)
		CFXDataPoint curveto = (*iterCurrent);
		// Offset curveto point
		if(curveto.getType() == DP_STATIC)
			curveto.setStaticPosition( curveto.getPosition() + vec3_t(0,0,10) );
		else
			curveto.setDynamicOffset( vec3_t(0,0,10) );
		// add to list
		pBezierPath->addPoints( (*iterCurrent), curveto );
	}
	return pBezierPath;
}

// Move points by specified vector
void CLinearPath::movePoints( vec3_t move )
{	std::list<CFXDataPoint>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_Points)
	{	iterCurrent->movePoint(move);
	}
}

// Rotate points by a specified angle
void CLinearPath::rotatePoints( vec3_t angle )
{	std::list<CFXDataPoint>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_Points)
	{	iterCurrent->rotatePoint(angle);
	}
}

// Get Point
vec3_t CLinearPath::getPoint( float flDist )
{	float flCurDist=0;
	float flStartDist=0;
	std::list<CFXDataPoint>::iterator iterCurrent;
	std::list<CFXDataPoint>::iterator iterNext;
	// Iterate through all points
	ForEachIn(iterCurrent, m_Points)
	{	flStartDist = flCurDist;
		iterNext = iterCurrent;
		iterNext++;
		// not last point
		if(iterNext != m_Points.end())
		{	// Get distance of segment
			float flSegmentDist = (iterCurrent->getPosition()-iterNext->getPosition()).Length();
			// Add to current
			flCurDist += flSegmentDist;
			// Found points our distance lies on
			if(flCurDist >= flDist)
			{	// Get distance relitive to points
				float flRelitive = flDist - flStartDist;
				// Get percent between points we're at
				float flPer = flRelitive/flSegmentDist;
				// Return point
				return (iterCurrent->getPosition() + (iterNext->getPosition()-iterCurrent->getPosition())*flPer);
			}
		}
		else
		{	return iterCurrent->getPosition();
		}
	}
	return vec3_t(0,0,0);
}

// Get Start Point
vec3_t CLinearPath::getStartPoint( void )
{	std::list<CFXDataPoint>::iterator iterStart = m_Points.begin();
	if(iterStart != NULL)
	{	return iterStart->getPosition();
	}
	return vec3_t(0,0,0);
}

// Get End Point
vec3_t CLinearPath::getEndPoint( void )
{	vec3_t vecEnd = vec3_t(0,0,0);
	std::list<CFXDataPoint>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_Points)
	{	vecEnd = iterCurrent->getPosition();
	}
	return vecEnd;
}

// Render
void CLinearPath::RenderSegment( bool bSelected )
{	// blue for linear
	gEngfuncs.pTriAPI->RenderMode( kRenderNormal );
	if(bSelected)
		gEngfuncs.pTriAPI->Color4ub(255,255,0,255);
	else
		gEngfuncs.pTriAPI->Color4ub(0,0,255,255);
	gEngfuncs.pTriAPI->CullFace( TRI_NONE );
	gEngfuncs.pTriAPI->Begin( TRI_LINES );

	std::list<CFXDataPoint>::iterator iterPoints;
	std::list<CFXDataPoint>::iterator iterPointsNext = NULL;
	ForEachIn(iterPoints, m_Points)
	{	iterPointsNext = iterPoints;
		iterPointsNext++;
		if(iterPointsNext != m_Points.end())
		{	gEngfuncs.pTriAPI->Vertex3fv( iterPoints->getPosition() );
			gEngfuncs.pTriAPI->Vertex3fv( iterPointsNext->getPosition() );
		}
		else
		{	break;
		}
	}
	gEngfuncs.pTriAPI->End();
}

// Write XML
TiXmlElement *CLinearPath::writeXML( void )
{	TiXmlElement *pElem = new TiXmlElement("segment");
	pElem->SetAttribute("type","linear");
	
	// Iterate through points and add to xml
	std::list<CFXDataPoint>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_Points)
	{	pElem->LinkEndChild(iterCurrent->writeXML());
	}
	return pElem;
}

// read XML
void CLinearPath::readXML( TiXmlElement *pElement )
{	if(!pElement)
	{	return;
	}
	// Iterate through children
	TiXmlElement *pPoints=NULL;
	IterateXML(pPoints,pElement)
	{	addPoint(CFXDataPoint(pPoints));
	}
}

/*******
* Bezier Points
*******/
// Constructor
CBezierPoints::CBezierPoints()
{	setFirst(false);
}

CBezierPoints::CBezierPoints( CFXDataPoint org, CFXDataPoint curveto )
{	CBezierPoints();
	setOrigin(org);
	setCurveTo(curveto);
}

/*******
* Bezier Path
*******/
// Constructor
CBezierPath::CBezierPath()
{	m_flLength = 0;
}

// Get point on curve (percent)
vec3_t CBezierPath::getPointOnCurveP( vec3_t a, vec3_t b, vec3_t c, vec3_t d, double s )
{	vec3_t v;
	for(int i=0;i<3;i++)
		v[i] = float(BezierCalc(a[i],b[i],c[i],d[i],s));
	return v;
}

// Get point on curve (distance)
vec3_t CBezierPath::getPointOnCurveD( CBezierPoints *bp1, CBezierPoints *bp2, double dist )
{	vec3_t a,b,c,d;
	if(bp1->isFirst())
		b = bp1->getCurveTo()->getPosition();
	else
		b = bp1->getInverseCurveTo();
	a = bp1->getOrigin()->getPosition();
	c = bp2->getCurveTo()->getPosition();
	d = bp2->getOrigin()->getPosition();
	return getPointOnCurveD(a,b,c,d,dist);
}

// Get point on curve (distance)
vec3_t CBezierPath::getPointOnCurveD( vec3_t a, vec3_t b, vec3_t c, vec3_t d, double dist )
{	// 0 distances, dont bother calculating
	if(dist <= 0)
		return a;

	// Get Total Distance
	float flTotalDist = getLengthBezierPoints(a,b,c,d);
	// Distance exceeds total distance of curve, just return last point of bezier curve
	if(dist > flTotalDist)
	{	return d;
	}
	// Get exact distance on curve using a simple brute force approach
	double dCurrentDist = 0;
	double dCurrentPercent = 0;
	double dIncrement;
	
	// Really long curve, we can get away with using cheap
	if(flTotalDist > BEZIER_USE_CHEAP)
	{	dIncrement = BEZIER_DETAIL_CHEAP;
	}
	// Decently small sized curve
	else
	{	dIncrement = BEZIER_DETAIL;
	}

	vec3_t vecPrev = a;
	vec3_t vecCalc;
	bool bIncrease = true;
	while(abs(dCurrentDist - dist) > BEZIER_DIST_DETAIL)
	{	
		// Get pointon curve
		vecCalc = getPointOnCurveP(d,c,b,a,dCurrentPercent);

		// Add to total distance
		if(bIncrease)
			dCurrentDist += (vecCalc - vecPrev).Length();
		else
			dCurrentDist -= (vecCalc - vecPrev).Length();

		// Reverse direction and reduce increment if we've stepped over our distance
		if((bIncrease && (dCurrentDist > dist)) || (!bIncrease && (dCurrentDist < dist)))
		{	bIncrease = !bIncrease;
			// Cut increment by 1/4th
			dIncrement *= double(.25);
		}
		
		// Add increment
		if(bIncrease)
			dCurrentPercent += dIncrement;
		else
			dCurrentPercent -= dIncrement;

		// Don't go over/below 100/0 percent
		if(dCurrentPercent > 1 || dCurrentPercent < 0)
			break;

		vecPrev = vecCalc;
	}
	// cap percent
	if(dCurrentPercent > 1)
		dCurrentPercent = 1;
	else if(dCurrentPercent < 0)
		dCurrentPercent = 0;

	return vecCalc;
}

// Get length between bezier points
float CBezierPath::getLengthBezierPoints( vec3_t a, vec3_t b, vec3_t c, vec3_t d )
{	// calculate bezier curve length
	int i=0;
	float flLength = 0;
	double s=1.0;
	vec3_t vecPrev = a;
	vec3_t vecCalc;
	do
	{	vecCalc = getPointOnCurveP(a,b,c,d,s);
		flLength += (vecCalc - vecPrev).Length();
		vecPrev = vecCalc;
		s-=BEZIER_DETAIL;
	}while(s>=0.0);
	return flLength;
}

// Get length between bezier points
float CBezierPath::getLengthBezierPoints( CBezierPoints *bp1, CBezierPoints *bp2 )
{	// Shorthand
	vec3_t a,b,c,d;
	if(bp1->isFirst())
		b = bp1->getCurveTo()->getPosition();
	else
		b = bp1->getInverseCurveTo();
	a = bp1->getOrigin()->getPosition();
	c = bp2->getCurveTo()->getPosition();
	d = bp2->getOrigin()->getPosition();

	return getLengthBezierPoints(a,b,c,d);
}

// Calculate Length
void CBezierPath::calculateLength( void )
{	double dLength=0;
	std::list<CBezierPoints>::iterator iterCurrent;
	std::list<CBezierPoints>::iterator iterNext;
	// Iterate through all points and calculate total distance
	ForEachIn(iterCurrent, m_BezierPoints)
	{	iterNext = iterCurrent;
		iterNext++;
		if(iterNext != m_BezierPoints.end())
			dLength += getLengthBezierPoints((&(*iterCurrent)), (&(*iterNext)));
		else
			break;
	}
	setLength(float(dLength));
}

// Add points
void CBezierPath::addPoints( CFXDataPoint dp1, CFXDataPoint dp2 )
{	dp1.setFXEvent(getFXEvent());
	dp2.setFXEvent(getFXEvent());
	// Create point
	CBezierPoints bp(dp1, dp2);
	// If first point, set it as so
	if(m_BezierPoints.empty())
	{	bp.setFirst(true);
	}
	else
	{	bp.setFirst(false);
	}
	// Add to list
	m_BezierPoints.push_back(bp);
	// Recalculate Length
	calculateLength();
}

// Change this linear path to a bezier path
CLinearPath	*CBezierPath::toLinearPath( void )
{	// Create new linear path
	CLinearPath *pLinearPath = new CLinearPath;

	// Iterate through all bezier points and convert to linear
	std::list<CBezierPoints>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_BezierPoints)
	{	pLinearPath->addPoint((*(iterCurrent->getOrigin())));
	}
	return pLinearPath;
}

// Move points by specified vector
void CBezierPath::movePoints( vec3_t move )
{	std::list<CBezierPoints>::iterator iterCurrent;
	// Iterate through all points and calculate total distance
	ForEachIn(iterCurrent, m_BezierPoints)
	{	iterCurrent->getOrigin()->movePoint( move );
		iterCurrent->getCurveTo()->movePoint( move );
	}
}

// Rotate points by a specified angle
void CBezierPath::rotatePoints( vec3_t angle )
{	std::list<CBezierPoints>::iterator iterCurrent;
	// Iterate through all points and calculate total distance
	ForEachIn(iterCurrent, m_BezierPoints)
	{	iterCurrent->getOrigin()->rotatePoint( angle );
		iterCurrent->getCurveTo()->rotatePoint( angle );
	}
}

// Get Point
vec3_t CBezierPath::getPoint( float flDist )
{	float flCurDist=0;
	float flStartDist=0;
	std::list<CBezierPoints>::iterator iterCurrent;
	std::list<CBezierPoints>::iterator iterNext;
	// Iterate through all points
	ForEachIn(iterCurrent, m_BezierPoints)
	{	flStartDist = flCurDist;
		iterNext = iterCurrent;
		iterNext++;
		// not last point
		if(iterNext != m_BezierPoints.end())
		{	// Get distance of segment
			float flSegmentDist = getLengthBezierPoints((&(*iterCurrent)), (&(*iterNext)));
			// Add to current
			flCurDist += flSegmentDist;
			// Found points our distance lies on
			if(flCurDist >= flDist)
			{	// Get distance relitive to points
				float flRelitive = flDist - flStartDist;
				// Return point
				return getPointOnCurveD((&(*iterCurrent)), (&(*iterNext)), flRelitive);
			}
		}
		else
			break;
	}
	return vec3_t(0,0,0);
}

// Get Start Point
vec3_t CBezierPath::getStartPoint( void )
{	std::list<CBezierPoints>::iterator iterStart = m_BezierPoints.begin();
	if(iterStart != NULL)
	{	return iterStart->getOrigin()->getPosition();
	}
	return vec3_t(0,0,0);
}

// Get End Point
vec3_t CBezierPath::getEndPoint( void )
{	std::list<CBezierPoints>::iterator iterCurrent;
	vec3_t vecEnd = vec3_t(0,0,0);
	// Iterate through all points
	ForEachIn(iterCurrent, m_BezierPoints)
	{	vecEnd = iterCurrent->getOrigin()->getPosition();
	}
	return vecEnd;
}

// Render
void CBezierPath::RenderSegment( bool bSelected )
{	std::list<CBezierPoints>::iterator iterBCurrent = m_BezierPoints.begin();
	std::list<CBezierPoints>::iterator iterBNext = iterBCurrent;
	iterBNext++;	

	// DRAW CURVE
	while(iterBNext != m_BezierPoints.end())
	{	// Red for bezier curve
		gEngfuncs.pTriAPI->RenderMode( kRenderNormal );
		if(bSelected)
			gEngfuncs.pTriAPI->Color4ub(255,255,0,255);
		else
			gEngfuncs.pTriAPI->Color4ub(255,0,0,255);
		gEngfuncs.pTriAPI->CullFace( TRI_NONE );
		gEngfuncs.pTriAPI->Begin( TRI_LINES );

		// calculate bezier curve length
		int i=0;
		double s=1.0;
		vec3_t vecPrev = iterBCurrent->getOrigin()->getPosition();
		vec3_t vecCalc;
		do
		{	if(iterBCurrent->isFirst())
			{	vecCalc = getPointOnCurveP(iterBCurrent->getOrigin()->getPosition(), iterBCurrent->getCurveTo()->getPosition(), iterBNext->getCurveTo()->getPosition(), iterBNext->getOrigin()->getPosition(), s);
			}
			else
			{	vecCalc = getPointOnCurveP(iterBCurrent->getOrigin()->getPosition(), iterBCurrent->getInverseCurveTo(), iterBNext->getCurveTo()->getPosition(), iterBNext->getOrigin()->getPosition(), s);
			}
			
			gEngfuncs.pTriAPI->Vertex3fv( vecPrev );
			gEngfuncs.pTriAPI->Vertex3fv( vecCalc );
			vecPrev = vecCalc;
			s-=(double(1)/double(50));
			// Draw last peice of curve
			if(s<0)
			{	gEngfuncs.pTriAPI->Vertex3fv( vecCalc );
				gEngfuncs.pTriAPI->Vertex3fv( iterBNext->getOrigin()->getPosition() );
			}
		}while(s>=0.0);

		gEngfuncs.pTriAPI->End();

		// Green for bezier control points
		gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd );
		gEngfuncs.pTriAPI->Color4ub(0,64,0,64);
		gEngfuncs.pTriAPI->CullFace( TRI_NONE );
		gEngfuncs.pTriAPI->Begin( TRI_LINES );

		vec3_t vS1,vS2,vE1,vE2;
		bool bDrawFirst = false;
		if(iterBCurrent->isFirst())
		{	vS1 = iterBCurrent->getOrigin()->getPosition();
			vS2 = iterBCurrent->getCurveTo()->getPosition();
			bDrawFirst = true;
		}
		// Last link, only draw part of the line
		std::list<CBezierPoints>::iterator iterLastLink = m_BezierPoints.end();
		iterLastLink--;
		if(iterBNext == iterLastLink)
		{	vE1 = iterBNext->getOrigin()->getPosition();
			vE2 = iterBNext->getCurveTo()->getPosition();
		}
		// Not last link, draw from curveto to curveto
		else
		{	vE1 = iterBNext->getCurveTo()->getPosition();
			vE2 = iterBNext->getInverseCurveTo();
		}
		if(bDrawFirst)
		{	gEngfuncs.pTriAPI->Vertex3fv( vS1 );
			gEngfuncs.pTriAPI->Vertex3fv( vS2 );
		}
		gEngfuncs.pTriAPI->Vertex3fv( vE1 );
		gEngfuncs.pTriAPI->Vertex3fv( vE2 );

		gEngfuncs.pTriAPI->End();

		// Next part of curve
		iterBCurrent++;
		iterBNext++;
	}
}

// Write XML
TiXmlElement *CBezierPath::writeXML( void )
{	TiXmlElement *pElem = new TiXmlElement("segment");
	pElem->SetAttribute("type","bezier");
	
	// Iterate through points and add to xml
	std::list<CBezierPoints>::iterator iterCurrent;
	ForEachIn(iterCurrent, m_BezierPoints)
	{	TiXmlElement *pPair = new TiXmlElement("pair");

		// Add data points to xml
		TiXmlElement *dp = iterCurrent->getOrigin()->writeXML();
		pPair->LinkEndChild(dp);
		dp = iterCurrent->getCurveTo()->writeXML();
		pPair->LinkEndChild(dp);

		// Add pair to main element
		pElem->LinkEndChild(pPair);
	}

	return pElem;
}

// read XML
void CBezierPath::readXML( TiXmlElement *pElement )
{	if(!pElement)
	{	return;
	}

	// Iterate through children
	TiXmlElement *pPair=NULL;
	IterateXML(pPair,pElement)
	{	TiXmlElement *dp1 = pPair->FirstChildElement();
		TiXmlElement *dp2 = pPair->FirstChildElement()->NextSiblingElement();
		addPoints(CFXDataPoint(dp1), CFXDataPoint(dp2));
	}
}