//
//	Copyright © 2005 Nemetschek North America 
//	All Rights Reserved.
//
// Created by Vladislav Stanev

#include "StdAfx.h"
//#include "VWLine2D.h"

using namespace VWFC::Math;

VWLine2D::VWLine2D()
{
}

VWLine2D::VWLine2D(double x1, double y1, double x2, double y2)
{
	this->SetLine( x1, y1, x2, y2 );
}

VWLine2D::VWLine2D(const VWPoint2D& begPt, const VWPoint2D& endPt)
{
	this->SetLine( begPt, endPt );
}

VWLine2D::VWLine2D(const VWLine2D& src)
{
	m_ptStart	= src.m_ptStart;
	m_ptEnd		= src.m_ptEnd;
}

VWLine2D::~VWLine2D()
{
}

VWLine2D& VWLine2D::operator=(const VWLine2D& src)
{
	m_ptStart	= src.m_ptStart;
	m_ptEnd		= src.m_ptEnd;
	return *this;
}

const VWPoint2D& VWLine2D::GetStart() const
{
	return m_ptStart;
}

VWPoint2D& VWLine2D::GetStart()
{
	return m_ptStart;
}

const VWPoint2D& VWLine2D::GetEnd() const
{
	return m_ptEnd;
}

VWPoint2D& VWLine2D::GetEnd()
{
	return m_ptEnd;
}

void VWLine2D::GetLine(VWPoint2D& outPtStart, VWPoint2D& outPtEnd) const
{
	outPtStart		= m_ptStart;
	outPtEnd		= m_ptEnd;
}

void VWLine2D::SetStart(const VWPoint2D& pt)
{
	m_ptStart		= pt;
}

void VWLine2D::SetEnd(const VWPoint2D& pt)
{
	m_ptEnd			= pt;
}

void VWLine2D::SetLine(double x1, double y1, double x2, double y2)
{
	m_ptStart.x		= x1;
	m_ptStart.y		= y1;
	m_ptEnd.x		= x2;
	m_ptEnd.y		= y2;
}

void VWLine2D::SetLine(const VWPoint2D& ptStart, const VWPoint2D& ptEnd)
{
	m_ptStart		= ptStart;
	m_ptEnd			= ptEnd;
}

void VWLine2D::ReverseSides()
{
	VWPoint2D	pt	= m_ptStart;
	m_ptStart		= m_ptEnd;
	m_ptEnd			= pt;
}

VWLine2D VWLine2D::operator+(const VWPoint2D& p) const
{
	VWLine2D	line	= *this;
	line				+= p;
	return line;
}

VWLine2D VWLine2D::operator-(const VWPoint2D& p) const
{
	VWLine2D	line	= *this;
	line				-= p;
	return line;
}

VWLine2D VWLine2D::operator*(double scalar) const
{
	VWLine2D	line	= *this;
	line				*= scalar;
	return line;
}

VWLine2D VWLine2D::operator/(double scalar) const
{
	VWLine2D	line	= *this;
	line				/= scalar;
	return line;
}

VWLine2D& VWLine2D::operator+=(const VWPoint2D& p)
{
	m_ptStart		+= p;
	m_ptEnd			+= p;
	return *this;
}

VWLine2D& VWLine2D::operator-=(const VWPoint2D& p)
{
	m_ptStart		-= p;
	m_ptEnd			-= p;
	return *this;
}

VWLine2D& VWLine2D::operator*=(double scalar)
{
	m_ptStart		*= scalar;
	m_ptEnd			*= scalar;
	return *this;
}

VWLine2D& VWLine2D::operator/=(double scalar)
{
	m_ptStart		/= scalar;
	m_ptEnd			/= scalar;
	return *this;
}

//////////////////////////////////////////////////////////////////////////
// math functions
//////////////////////////////////////////////////////////////////////////

// return the center point of the line
VWPoint2D VWLine2D::CenterPt() const
{
	return ( m_ptStart + m_ptEnd ) / 2;
}

// return the length of the line
double VWLine2D::GetLength() const
{
	return (m_ptStart-m_ptEnd).Magnitude();
}

void VWLine2D::CalcLineEquation(double& outA, double& outB, double& outC)
{
	VWPoint2D	dir	= m_ptEnd - m_ptStart;
	VWPoint2D	perp( - dir.y, dir.x );
	perp.Normalize();

	outA			= perp.x;
	outB			= perp.y;
	outC			= - (m_ptStart.x * outA + m_ptStart.y * outB);
}

// return true if the specified point is on the line within an epsilon
bool VWLine2D::PtOnLine(const VWPoint2D& pt, double dEpsilon) const
{
	return VWLine2D::PtOnLine( pt, m_ptStart, m_ptEnd, dEpsilon );
}

bool VWLine2D::PtOnLine(const VWPoint2D& pt, const VWPoint2D& lineA, const VWPoint2D& lineB, double dEpsilon)
{
	bool		bPtOnLine	= false;
	double		AB			= (lineB - lineA).Magnitude();
	double		AC			= (pt - lineA).Magnitude();
	double		CB			= (lineB - pt).Magnitude();

	if ( Abs( AB - (AC + CB) ) < dEpsilon ) {
		VWPoint2D	temp_pt		= pt;
		temp_pt.RelativeCoords( lineA, lineB );
		bPtOnLine				= ( temp_pt.y < 0 ? -temp_pt.y : temp_pt.y ) < dEpsilon;
	}

	return bPtOnLine;
}

// get the angle 0 .. 90 from this line to the specified line
double VWLine2D::GetAngle90(const VWLine2D& line) const
{
	double_gs	lineLen		= line.GetLength();
	if ( DoubleIsNearlyZero( lineLen ) )
		return 0.0;

	VWPoint2D	projB1		= this->PtPerpLine( line.m_ptStart );
	VWPoint2D	projB2		= this->PtPerpLine( line.m_ptEnd );
	double		mag			= ( projB1 - projB2 ).Magnitude();
	double		cosalpha	= mag / lineLen;
	if ( cosalpha+0.00001 > 1 )
		cosalpha = 1.0;
	double		alpha		= acos( cosalpha );

	return MathUtils::Rad2Deg( alpha );
}

VWPoint2D VWLine2D::GetDirection() const
{
	VWPoint2D	dir		= m_ptEnd - m_ptStart;
	dir.Normalize();
	return dir;
}

// teturns a point on this line which is closest to the input point.
VWPoint2D VWLine2D::PtPerpLine(const VWPoint2D& pt) const
{
	VWPoint2D		temp_pt		= this->RelativeCoords( pt );

	VWPoint2D		e			= (m_ptEnd - m_ptStart).Normalize();
	VWPoint2D		perpLine	= m_ptStart + (e * temp_pt.x);
	return perpLine;
}

VWPoint2D VWLine2D::PtPerpLine(double x, double y) const
{
	return this->PtPerpLine( VWPoint2D( x, y ) );
}

// return the distance from the specified point to the line
double VWLine2D::Distance(const VWPoint2D& pt) const
{
	return this->Distance( pt.x, pt.y );
}

double VWLine2D::Distance(double x, double y) const
{
	double	distance	= VWLine2D::Distance( x, y, m_ptStart.x, m_ptStart.y, m_ptEnd.x, m_ptEnd.y );

#ifdef _DEBUG
	// innser test of the routine
	VWPoint2D	pt( x, y );
	VWPoint2D	projPt	= this->PtPerpLine( pt );
	double	oldWayDist	= projPt.DistanceTo( pt );
	VWFC_ASSERT( Abs(distance-oldWayDist) < 0.0001 );
#endif

	return distance;
}

// return true if the specified two points are on the same side of the line
bool VWLine2D::AreOnSameSide(double x1, double y1, double x2, double y2)
{
	double A, B, C;
	this->CalcLineEquation( A, B, C );
	bool sign1	= A * x1 + B * y1 + C >= 0;
	bool sign2	= A * x2 + B * y2 + C >= 0;
	return sign1 == sign2;
}

bool VWLine2D::AreOnSameSide(const VWPoint2D& a, const VWPoint2D& b)
{
	return this->AreOnSameSide( a.x, a.y, b.x, b.y );
}

// intersect the infinite lines. The result point can be outside the line segments
// return false if the lines are parallel
bool VWLine2D::IntersectLines(const VWLine2D& line, VWPoint2D& outIntersPt) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, line.m_ptStart, line.m_ptEnd, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	return ! bParallel;
}

bool VWLine2D::IntersectLines(const VWPoint2D& a, const VWPoint2D& b, VWPoint2D& outIntersPt) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, a, b, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	return ! bParallel;
}

// intersect the infinite lines. The result point can be outside the line segments
// return false if the lines are parallel
// outPtOnLineSegs returns true if the intersection point lies on both line segments
bool VWLine2D::IntersectLines(const VWLine2D& line, VWPoint2D& outIntersPt, bool& outPtOnLineSegs) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, line.m_ptStart, line.m_ptEnd, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	outPtOnLineSegs		= bIntOnSeg1 && bIntOnSeg2;
	return ! bParallel;
}

bool VWLine2D::IntersectLines(const VWPoint2D& a, const VWPoint2D& b, VWPoint2D& outIntersPt, bool& outPtOnLineSegs) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, a, b, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	outPtOnLineSegs		= bIntOnSeg1 && bIntOnSeg2;
	return ! bParallel;
}

// interset the line segments.
// return false if the lines are parallel or the line segments
// doest intersect each oter in the bound of the segments
bool VWLine2D::IntersectLineSegments(const VWLine2D& line, VWPoint2D& outIntersPt) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, line.m_ptStart, line.m_ptEnd, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	return ! bParallel && bIntOnSeg1 && bIntOnSeg2;
}

bool VWLine2D::IntersectLineSegments(const VWPoint2D& a, const VWPoint2D& b, VWPoint2D& outIntersPt) const
{
	bool			bParallel, bIntOnSeg1, bIntOnSeg2;
	VWLine2D::IntersectLines( this->m_ptStart, this->m_ptEnd, a, b, outIntersPt, bParallel, bIntOnSeg1, bIntOnSeg2 );
	return ! bParallel && bIntOnSeg1 && bIntOnSeg2;
}

// return translated point into a coordinate system defined by this line.
VWPoint2D VWLine2D::RelativeCoords(const VWPoint2D& pt) const
{
	VWPoint2D		p	= pt;
	p.RelativeCoords( m_ptStart, m_ptEnd );
	return p;
}

// rotate this point at specified angle in degrees and center point
void VWLine2D::RotateAtDeg(const VWPoint2D& centerPt, double_gs dDegAngle)
{
	m_ptStart.RotateAtDeg( centerPt, dDegAngle );
	m_ptEnd.RotateAtDeg( centerPt, dDegAngle );
}

// rotate this point at specified angle in radians and center point
void VWLine2D::RotateAtRad(const VWPoint2D& centerPt, double_gs dRadAngle)
{
	m_ptStart.RotateAtRad( centerPt, dRadAngle );
	m_ptEnd.RotateAtRad( centerPt, dRadAngle );
}

// intersects line [(p1)(p2)] with [(p3)(p4)]
void VWLine2D::IntersectLines(const VWPoint2D& p1, const VWPoint2D& p2, const VWPoint2D& p3, const VWPoint2D& p4, VWPoint2D& outInters, bool& outParallel, bool& outOnSegment1, bool& outOnSegment2)
{
	VWLine2D::IntersectLines( p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, outInters.x, outInters.y, outParallel, outOnSegment1, outOnSegment2, VWPoint2D::sEpsilon );
}

// intersects line [(x1,y1)(x2,y2)] with [(x3,y3)(x4,y4)]
void VWLine2D::IntersectLines(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double& outIntersX, double& outIntersY, bool& outParallel, bool& outOnSegment1, bool& outOnSegment2, double dEpsilon)
{
	double	t		= ( (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) );
	if ( Abs(t) > dEpsilon ) {
		double	ua		= ( (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3) ) / t;
		double	ub		= ( (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3) ) / t;

		outIntersX		= x1 + ua * (x2 - x1);
		outIntersY		= y1 + ua * (y2 - y1);

		outParallel		= Abs(ua) < dEpsilon && Abs(ub) < dEpsilon;
		outOnSegment1	= ua > - dEpsilon && ua < 1.0 + dEpsilon;
		outOnSegment2	= ub > - dEpsilon && ub < 1.0 + dEpsilon;
	}
	else {
		outParallel		= true;
		outOnSegment1	= false;
		outOnSegment2	= false;
	}
}

// distance from point (x0,y0) to line [(x1,y1)(x2,y2)]
double VWLine2D::Distance(double x0, double y0, double x1, double y1, double x2, double y2 )
{
	// http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
	//
	//     |(x2 - x1)(y1 - y0) - (x1 - x0)(y2 - y1)|
	// d = -----------------------------------------
	//        sqrt( (x2 - x1)^2 + (y2 - y1)^2 )

	// prevent not 0 for the 'sqrt'
	if ( Abs((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)) < 0.000001 )
		return 0;

	double	a		= (x2 - x1)*(y1 - y0) - (x1 - x0)*(y2 - y1);
	double	b		= Sqrt( (x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1) );
	double	dist	= Abs(a) / b;

#ifdef _DEBUG
	// innser test of the routine
	VWPoint2D	pt( x0, y0 );
	VWLine2D	line( x1, y1, x2, y2 );
	VWPoint2D	projPt	= line.PtPerpLine( pt );
	double	oldWayDist	= projPt.DistanceTo( pt );
	VWFC_ASSERT( Abs(dist-oldWayDist) < 0.0001 );
#endif

	return dist;
}
