424 lines
10 KiB
C++
Raw Permalink Normal View History

/*
** 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;
}
}