/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// FILE: Line2D.cpp ///////////////////////////////////////////////////////////////////////////////
// Author: Colin Day, January 2002
// Desc:   Some helpful 2D stuff
///////////////////////////////////////////////////////////////////////////////////////////////////

// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"	// This must go first in EVERY cpp file int the GameEngine

#include "Lib/BaseType.h"
#include "GameClient/Line2D.h"

// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
#define CLIP_LEFT   0x01
#define CLIP_RIGHT  0x02
#define CLIP_BOTTOM 0x04
#define CLIP_TOP	  0x08

// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////


// STATIC /////////////////////////////////////////////////////////////////////////////////////////
const static Coord2D reallyFarPoint = { 1000000.0, 1000000.0 };

//-------------------------------------------------------------------------------------------------
/** Clip a line to the region provided.  The source line runs from p1 to p2, and is clipped
	* using the clipRegion.  The resulting line goes from c1 to c2
	*
	* Return values: 
	*				TRUE  - Line is visible
	*				FALSE - Line is not visible
	*/
//-------------------------------------------------------------------------------------------------
Bool ClipLine2D( ICoord2D *p1, ICoord2D *p2, ICoord2D *c1, ICoord2D *c2,
								 IRegion2D *clipRegion )
{
	Int x1, y1, x2, y2;
	Int clipLeft;
	Int clipRight;
	Int clipTop;
	Int clipBottom;
	Int clipCode1;
	Int clipCode2;
	Int diff;

	// Use clip window that includes bottom right pixel
	clipLeft = clipRegion->lo.x;
	clipRight = clipRegion->hi.x;
	clipTop = clipRegion->lo.y;
	clipBottom = clipRegion->hi.y;
/*
	clipLeft = gfxCurrentContext->clipRect1.left;
	clipRight = gfxCurrentContext->clipRect1.right;
	clipTop = gfxCurrentContext->clipRect1.top;
	clipBottom = gfxCurrentContext->clipRect1.bottom;

	x1 = *px1;
	y1 = *py1;
	x2 = *px2;
	y2 = *py2;
*/

	x1 = p1->x;
	y1 = p1->y;
	x2 = p2->x;
	y2 = p2->y;
		
	// Test first point
	clipCode1 = 0;

	if (x1 < clipLeft)
		clipCode1 = CLIP_LEFT;
	else
	if (x1 > clipRight)
		clipCode1 = CLIP_RIGHT;

	if (y1 < clipTop)
		clipCode1 |= CLIP_TOP;
	else
	if (y1 > clipBottom)
		clipCode1 |= CLIP_BOTTOM;


	// Test second point
	clipCode2 = 0;

	if (x2 < clipLeft)
		clipCode2 = CLIP_LEFT;
	else
	if (x2 > clipRight)
		clipCode2 = CLIP_RIGHT;

	if (y2 < clipTop)
		clipCode2 |= CLIP_TOP;
	else
	if (y2 > clipBottom)
		clipCode2 |= CLIP_BOTTOM;


	// Both points inside window?
	if ((clipCode1 | clipCode2) == 0)
	{

		*c1 = *p1;
		*c2 = *p2;
		return TRUE;

	}  // end if

	// Both points outside window?
	if (clipCode1 & clipCode2)
		return FALSE;

	// First point outside window?
	if (clipCode1)
	{
		if (clipCode1 & CLIP_TOP)
		{
			if ((diff = (y2 - y1)) == 0)
				return FALSE;
			x1 += (x2 - x1) * (clipTop - y1) / diff;
			y1 = clipTop;
		}
		else
		if (clipCode1 & CLIP_BOTTOM)
		{
			if ((diff = (y2 - y1)) == 0)
				return FALSE;
			x1 += (x2 - x1) * (clipBottom - y1) / diff;
			y1 = clipBottom;
		}

		if (x1 > clipRight)
		{
			if ((diff = (x2 - x1)) == 0)
				return FALSE;
			y1 += (y2 - y1) * (clipRight - x1) / diff;
			x1 = clipRight;
		}
		else
		if (x1 < clipLeft)
		{
			if ((diff = (x2 - x1)) == 0)
				return FALSE;
			y1 += (y2 - y1) * (clipLeft - x1) / diff;
			x1 = clipLeft;
		}
	}

	// Second point outside window?
	if (clipCode2)
	{
		if (clipCode2 & CLIP_TOP)
		{
			if ((diff = (y2 - y1)) == 0)
				return FALSE;
			x2 += (x2 - x1) * (clipTop - y2) / diff;
			y2 = clipTop;
		}
		else
		if (clipCode2 & CLIP_BOTTOM)
		{
			if ((diff = (y2 - y1)) == 0)
				return FALSE;
			x2 += (x2 - x1) * (clipBottom - y2) / diff;
			y2 = clipBottom;
		}

		if (x2 > clipRight)
		{
			if ((diff = (x2 - x1)) == 0)
				return FALSE;
			y2 += (y2 - y1) * (clipRight - x2) / diff;
			x2 = clipRight;
		}
		else
		if (x2 < clipLeft)
		{
			if ((diff = (x2 - x1)) == 0)
				return FALSE;
			y2 += (y2 - y1) * (clipLeft - x2) / diff;
			x2 = clipLeft;
		}
	}

  c1->x = x1;
	c1->y = y1;
	c2->x = x2;
	c2->y = y2;

/*
	*px1 = x1;
	*py1 = y1;
	*px2 = x2;
	*py2 = y2;
*/

	// Line is visible
	return (x1 >= clipLeft && x1 <= clipRight &&
		    y1 >= clipTop && y1 <= clipBottom &&
			x2 >= clipLeft && x2 <= clipRight &&
			y2 >= clipTop && y2 <= clipBottom);

}  // end ClipLine2D


// This solution uses the 
// http://www.faqs.org/faqs/graphics/algorithms-faq/ 
// Subject 1.03
Bool IntersectLine2D( const Coord2D *a, const Coord2D *b, 
										   const Coord2D *c, const Coord2D *d, 
											 Coord2D *intersection)
{
	if (!a || !b || !c || !d) {
		// sanity. Lines that do not have endpoints do not intersect.
		return false;
	}

	Real r, s, denom;

	denom = ((b->x - a->x) * (d->y - c->y) - (b->y - a->y) * (d->x - c->x));
	if (denom == 0) {
		// the lines are parallel.
		return false;
	}

	r = ((a->y - c->y) * (d->x - c->x) - (a->x - c->x) * (d->y - c->y) ) / denom;
	s = ((a->y - c->y) * (b->x - a->x) - (a->x - c->x) * (b->y - a->y) ) / denom;

	if (0 <= r && r <= 1 && 0 <= s && s <= 1) {
		// The lines intersect.
		if (intersection) {
			intersection->x = a->x + r * (b->x - a->x);
			intersection->y = a->y + r * (b->y - a->y);
		}

		return true;
	}

	return false;
}

// determines whether a point lies within a rectangle. Doesnt' determine whether the shape is
// actually a rectangle or not.
Bool PointInsideRect2D(const Coord2D *bl, const Coord2D *tl, const Coord2D *br, const Coord2D *tr, 
											 const Coord2D *inputPoint)
{
	if (!(bl && br && tl && tr && inputPoint)) {
		return FALSE;
	}

	Real uVal;
	// we're actually only interested in if the U value is (0,1)
	ShortestDistancePointToSegment2D(bl, tl, inputPoint, NULL, NULL, &uVal);
	if (uVal <= 0.0f || uVal >= 1.0f) {
		return false;
	}

	ShortestDistancePointToSegment2D(bl, br, inputPoint, NULL, NULL, &uVal);

	return (uVal > 0.0f && uVal < 1.0f);
}

// convenience. Just prunes out the Z coordinate for a call to PointInsideRect2D
Bool PointInsideRect3D(const Coord3D *bl, const Coord3D *tl, const Coord3D *br, const Coord3D *tr,
											 const Coord3D *inputPoint)
{
	Coord2D bl2d, tl2d, br2d, tr2d, pt;
	bl2d.x = bl->x;
	bl2d.y = bl->y;
	tl2d.x = tl->x;
	tl2d.y = tl->y;
	br2d.x = br->x;
	br2d.y = br->y;
	tr2d.x = tr->x;
	tr2d.y = tr->y;

	pt.x = inputPoint->x;
	pt.y = inputPoint->y;

	return PointInsideRect2D(&bl2d, &br2d, &tl2d, &tr2d, &pt);
}


// This function uses even-odd winding to determine whether a point is inside an area.
Bool PointInsideArea2D(const Coord2D *ptToTest, const Coord2D *area, const Int numPointsInArea)
{
	int numIntersections = 0;
	for (int i = 0; i < numPointsInArea; ++i) {
		if (IntersectLine2D(ptToTest, &reallyFarPoint, &area[i], &area[(i + 1) % numPointsInArea])) {
			++numIntersections;
		}
	}
	return (numIntersections % 2 == 1);
}

// This function uses even-odd winding to determine whether a point is inside an area.
Bool PointInsideArea2D( const Coord3D *ptToTest, const Coord3D *area, Int numPointsInArea)
{
	int numIntersections = 0;
	Coord2D pt2D, area2D1, area2D2;
	pt2D.x = ptToTest->x;
	pt2D.y = ptToTest->y;

	for (int i = 0; i < numPointsInArea; ++i) {
		area2D1.x = area[i].x;
		area2D1.y = area[i].y;
		area2D2.x = area[(i + 1) % numPointsInArea].x;
		area2D2.y = area[(i + 1) % numPointsInArea].y;
		if (IntersectLine2D(&pt2D, &reallyFarPoint, &area2D1, &area2D2)) {
			++numIntersections;
		}
	}
	return (numIntersections % 2 == 1);
}

///< Checks if a point is inside a perfect rectangle (top left and bottom right)
Bool Coord3DInsideRect2D( const Coord3D *inputPoint, const Coord2D *tl, const Coord2D *br )
{
	if( inputPoint->x >= tl->x && inputPoint->x <= br->x )
	{
		if( inputPoint->y >= tl->y && inputPoint->y <= br->y )
		{
			return TRUE;
		}
	}
	return FALSE;
}

///< Scales a rect by a factor either growing or shrinking it.
void ScaleRect2D( Coord2D *tl, Coord2D *br, Real scaleFactor )
{
	scaleFactor = scaleFactor-1.0f; // We are starting with tl,br, so scaling it by 1 means adding 0 to it.

	Real deltaWidth = (br->x - tl->x) * scaleFactor * 0.5f;
	Real deltaHeight = (br->y - tl->y) * scaleFactor * 0.5f;

	tl->x -= deltaWidth;
	tl->y -= deltaHeight;

	br->x += deltaWidth;
	br->y += deltaHeight;
}


// Solution taken from http://astronomy.swin.edu.au/~pbourke/geometry/pointline/
void ShortestDistancePointToSegment2D( const Coord2D *a, const Coord2D *b, const Coord2D *pt,
																			 Real *outDistance, Coord2D *outPosition, Real *outU )
{
	if (!a || !b || !pt) {
		return;
	}

	if (a->x == b->x && a->y == b->y) {
		// special case, its simply pt to pt.
		Coord2D segment;
		segment.x = pt->x - a->x;
		segment.y = pt->y - a->y;
		if (outDistance) {		
			(*outDistance) = segment.length();
		}

		if (outPosition) {
			(*outPosition).x = a->x;
			(*outPosition).y = a->y;
		}

		if (outU) {
			(*outU) = 0.5;
		}
		return;
	}

	Coord2D segAB;
	segAB.x = b->x - a->x;
	segAB.y = b->y - a->y;

	// General case
	Real u = ((pt->x - a->x) * (b->x - a->x) + (pt->y - a->y) * (b->y - a->y)) /
					 sqr(segAB.length());

	Coord2D intersectSegment;

	intersectSegment.x = a->x + u * (b->x - a->x);
	intersectSegment.y = a->y + u * (b->y - a->y);


	if (outPosition) {
		(*outPosition) = intersectSegment;
	}

	if (outDistance) {
		(*outDistance) = intersectSegment.length();
	}
	
	if (outU) {
		(*outU) = u;
	}

}
