kicad/thirdparty/other_math/math_for_graphics.cpp

498 lines
13 KiB
C++

// math for graphics utility routines and RC, from FreePCB
#include <vector>
#include <cmath>
#include <float.h>
#include <limits.h>
#include <cstdlib> // for abs function on ints
#include <algorithm>
#include <math_for_graphics.h>
#include <math/util.h>
static bool InRange( double x, double xi, double xf );
/* Function FindLineSegmentIntersection
* find intersection between line y = a + bx and line segment (xi,yi) to (xf,yf)
* if b > DBL_MAX/10, assume vertical line at x = a
* return false if no intersection or true if intersect
* return coords of intersections in *x1, *y1, *x2, *y2
* if no intersection, returns min distance in dist
*/
bool FindLineSegmentIntersection( double a, double b, int xi, int yi, int xf, int yf,
double& x1, double& y1, double* dist )
{
double xx = 0, yy = 0; // Init made to avoid C compil "uninitialized" warning
bool bVert = false;
if( b > DBL_MAX / 10.0 )
bVert = true;
if( xf != xi ) // non-vertical segment, get intersection
{
// horizontal or oblique straight segment
// put into form y = c + dx;
double d = (double) (yf - yi) / (double) (xf - xi);
double c = yf - d * xf;
if( bVert )
{
// if vertical line, easy
if( InRange( a, xi, xf ) )
{
x1 = a;
y1 = c + d * a;
return true;
}
else
{
if( dist )
*dist = std::min( std::abs( a - xi ), std::abs( a - xf ) );
return false;
}
}
if( std::abs( b - d ) < 1E-12 )
{
// parallel lines
if( dist )
{
*dist = GetPointToLineDistance( a, b, xi, xf );
}
return false; // lines parallel
}
// calculate intersection
xx = (c - a) / (b - d);
yy = a + b * (xx);
// see if intersection is within the line segment
if( yf == yi )
{
// horizontal line
if( (xx>=xi && xx>xf) || (xx<=xi && xx<xf) )
return false;
}
else
{
// oblique line
if( (xx>=xi && xx>xf) || (xx<=xi && xx<xf)
|| (yy>yi && yy>yf) || (yy<yi && yy<yf) )
return false;
}
}
else
{
// vertical line segment
if( bVert )
return false;
xx = xi;
yy = a + b * xx;
if( (yy>=yi && yy>yf) || (yy<=yi && yy<yf) )
return false;
}
x1 = xx;
y1 = yy;
return true;
}
/*
* Function TestForIntersectionOfStraightLineSegments
* Test for intersection of line segments
* If lines are parallel, returns false
* If true, returns also intersection coords in x, y
* if false, returns min. distance in dist (may be 0.0 if parallel)
*/
bool TestForIntersectionOfStraightLineSegments( int x1i, int y1i, int x1f, int y1f,
int x2i, int y2i, int x2f, int y2f,
int* x, int* y, double* d )
{
double a, b, dist;
// first, test for intersection
if( x1i == x1f && x2i == x2f )
{
// both segments are vertical, can't intersect
}
else if( y1i == y1f && y2i == y2f )
{
// both segments are horizontal, can't intersect
}
else if( x1i == x1f && y2i == y2f )
{
// first seg. vertical, second horizontal, see if they cross
if( InRange( x1i, x2i, x2f )
&& InRange( y2i, y1i, y1f ) )
{
if( x )
*x = x1i;
if( y )
*y = y2i;
if( d )
*d = 0.0;
return true;
}
}
else if( y1i == y1f && x2i == x2f )
{
// first seg. horizontal, second vertical, see if they cross
if( InRange( y1i, y2i, y2f )
&& InRange( x2i, x1i, x1f ) )
{
if( x )
*x = x2i;
if( y )
*y = y1i;
if( d )
*d = 0.0;
return true;
}
}
else if( x1i == x1f )
{
// first segment vertical, second oblique
// get a and b for second line segment, so that y = a + bx;
b = double( y2f - y2i ) / (x2f - x2i);
a = (double) y2i - b * x2i;
double x1, y1;
bool test = FindLineSegmentIntersection( a, b, x1i, y1i, x1f, y1f,
x1, y1 );
if( test )
{
if( InRange( y1, y1i, y1f ) && InRange( x1, x2i, x2f ) && InRange( y1, y2i, y2f ) )
{
if( x )
*x = KiROUND( x1 );
if( y )
*y = KiROUND( y1 );
if( d )
*d = 0.0;
return true;
}
}
}
else if( y1i == y1f )
{
// first segment horizontal, second oblique
// get a and b for second line segment, so that y = a + bx;
b = double( y2f - y2i ) / (x2f - x2i);
a = (double) y2i - b * x2i;
double x1, y1;
bool test = FindLineSegmentIntersection( a, b, x1i, y1i, x1f, y1f, x1, y1 );
if( test )
{
if( InRange( x1, x1i, x1f ) && InRange( x1, x2i, x2f ) && InRange( y1, y2i, y2f ) )
{
if( x )
*x = KiROUND( x1 );
if( y )
*y = KiROUND( y1 );
if( d )
*d = 0.0;
return true;
}
}
}
else if( x2i == x2f )
{
// second segment vertical, first oblique
// get a and b for first line segment, so that y = a + bx;
b = double( y1f - y1i ) / (x1f - x1i);
a = (double) y1i - b * x1i;
double x1, y1;
bool test = FindLineSegmentIntersection( a, b, x2i, y2i, x2f, y2f, x1, y1 );
if( test )
{
if( InRange( x1, x1i, x1f ) && InRange( y1, y1i, y1f ) && InRange( y1, y2i, y2f ) )
{
if( x )
*x = KiROUND( x1 );
if( y )
*y = KiROUND( y1 );
if( d )
*d = 0.0;
return true;
}
}
}
else if( y2i == y2f )
{
// second segment horizontal, first oblique
// get a and b for second line segment, so that y = a + bx;
b = double( y1f - y1i ) / (x1f - x1i);
a = (double) y1i - b * x1i;
double x1, y1;
bool test = FindLineSegmentIntersection( a, b, x2i, y2i, x2f, y2f, x1, y1 );
if( test )
{
if( InRange( x1, x1i, x1f ) && InRange( y1, y1i, y1f ) )
{
if( x )
*x = KiROUND( x1 );
if( y )
*y = KiROUND( y1 );
if( d )
*d = 0.0;
return true;
}
}
}
else
{
// both segments oblique
if( long( y1f - y1i ) * (x2f - x2i) != long( y2f - y2i ) * (x1f - x1i) )
{
// not parallel, get a and b for first line segment, so that y = a + bx;
b = double( y1f - y1i ) / (x1f - x1i);
a = (double) y1i - b * x1i;
double x1, y1;
bool test = FindLineSegmentIntersection( a, b, x2i, y2i, x2f, y2f, x1, y1 );
// both segments oblique
if( test )
{
if( InRange( x1, x1i, x1f ) && InRange( y1, y1i, y1f ) )
{
if( x )
*x = KiROUND( x1 );
if( y )
*y = KiROUND( y1 );
if( d )
*d = 0.0;
return true;
}
}
}
}
// don't intersect, get shortest distance between each endpoint and the other line segment
dist = GetPointToLineSegmentDistance( x1i, y1i, x2i, y2i, x2f, y2f );
double xx = x1i;
double yy = y1i;
double dd = GetPointToLineSegmentDistance( x1f, y1f, x2i, y2i, x2f, y2f );
if( dd < dist )
{
dist = dd;
xx = x1f;
yy = y1f;
}
dd = GetPointToLineSegmentDistance( x2i, y2i, x1i, y1i, x1f, y1f );
if( dd < dist )
{
dist = dd;
xx = x2i;
yy = y2i;
}
dd = GetPointToLineSegmentDistance( x2f, y2f, x1i, y1i, x1f, y1f );
if( dd < dist )
{
dist = dd;
xx = x2f;
yy = y2f;
}
if( x )
*x = KiROUND( xx );
if( y )
*y = KiROUND( yy );
if( d )
*d = dist;
return false;
}
/* Function GetClearanceBetweenSegments
* Get clearance between 2 segments
* Returns coordinates of the closest point between these 2 segments in x, y
* If clearance > max_cl, just returns max_cl+1 and doesn't return x,y
*/
int GetClearanceBetweenSegments( int x1i, int y1i, int x1f, int y1f, int w1,
int x2i, int y2i, int x2f, int y2f, int w2,
int max_cl, int* x, int* y )
{
// check clearance between bounding rectangles
int min_dist = max_cl + ( (w1 + w2) / 2 );
if( std::min( x1i, x1f ) - std::max( x2i, x2f ) > min_dist )
return max_cl+1;
if( std::min( x2i, x2f ) - std::max( x1i, x1f ) > min_dist )
return max_cl+1;
if( std::min( y1i, y1f ) - std::max( y2i, y2f ) > min_dist )
return max_cl+1;
if( std::min( y2i, y2f ) - std::max( y1i, y1f ) > min_dist )
return max_cl+1;
int xx, yy;
double dist;
TestForIntersectionOfStraightLineSegments( x1i, y1i, x1f, y1f,
x2i, y2i, x2f, y2f, &xx, &yy, &dist );
int d = KiROUND( dist ) - ((w1 + w2) / 2);
if( d < 0 )
d = 0;
if( x )
*x = xx;
if( y )
*y = yy;
return d;
}
/* Function GetPointToLineDistance
* Get min. distance from (x,y) to line y = a + bx
* if b > DBL_MAX/10, assume vertical line at x = a
* returns closest point on line in xpp, ypp
*/
double GetPointToLineDistance( double a, double b, int x, int y, double* xpp, double* ypp )
{
if( b > DBL_MAX / 10 )
{
// vertical line
if( xpp && ypp )
{
*xpp = a;
*ypp = y;
}
return std::abs( a - x );
}
// find c,d such that (x,y) lies on y = c + dx where d=(-1/b)
double d = -1.0 / b;
double c = (double) y - d * x;
// find nearest point to (x,y) on line through (xi,yi) to (xf,yf)
double xp = (a - c) / (d - b);
double yp = a + b * xp;
if( xpp && ypp )
{
*xpp = xp;
*ypp = yp;
}
// find distance
return Distance( x, y, xp, yp );
}
/**
* Function GetPointToLineSegmentDistance
* Get distance between line segment and point
* @param x,y = point
* @param xi,yi Start point of the line segament
* @param xf,yf End point of the line segment
* @return the distance
*/
double GetPointToLineSegmentDistance( int x, int y, int xi, int yi, int xf, int yf )
{
// test for vertical or horizontal segment
if( xf==xi )
{
// vertical line segment
if( InRange( y, yi, yf ) )
return std::abs( x - xi );
else
return std::min( Distance( x, y, xi, yi ), Distance( x, y, xf, yf ) );
}
else if( yf==yi )
{
// horizontal line segment
if( InRange( x, xi, xf ) )
return std::abs( y - yi );
else
return std::min( Distance( x, y, xi, yi ), Distance( x, y, xf, yf ) );
}
else
{
// oblique segment
// find a,b such that (xi,yi) and (xf,yf) lie on y = a + bx
double b = (double) (yf - yi) / (xf - xi);
double a = (double) yi - b * xi;
// find c,d such that (x,y) lies on y = c + dx where d=(-1/b)
double d = -1.0 / b;
double c = (double) y - d * x;
// find nearest point to (x,y) on line through (xi,yi) to (xf,yf)
double xp = (a - c) / (d - b);
double yp = a + b * xp;
// find distance
if( InRange( xp, xi, xf ) && InRange( yp, yi, yf ) )
return Distance( x, y, xp, yp );
else
return std::min( Distance( x, y, xi, yi ), Distance( x, y, xf, yf ) );
}
}
// test for value within range
bool InRange( double x, double xi, double xf )
{
if( xf > xi )
{
if( x >= xi && x <= xf )
return true;
}
else
{
if( x >= xf && x <= xi )
return true;
}
return false;
}