package net.sourceforge.fidocadj.geom; /** Calculate geometric distances between a given point and a few geometric objects. <pre> This file is part of FidoCadJ. FidoCadJ 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. FidoCadJ 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 FidoCadJ. If not, @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>. Copyright 2008-2017 by Davide Bucci </pre> @author Davide Bucci */ public final class GeometricDistances { public static final int MIN_DISTANCE = 100; // Number of segments evaluated when calculatin the distance between a // point and a Bézier curve. public static final int MAX_BEZIER_SEGMENTS=10; // Some caching data private static int idx; private static int idy; private static int it; private static int ixmin, ixmax, iymin, iymax; private static double dx; private static double dy; private static double t; private static double xmin, ymin, xmax, ymax; private static int i, j; private static boolean c; private GeometricDistances() { // Does nothing. } /** Calculate the euclidean distance between two points. The distance calculated here is not accurate. It is meant only to discriminate objects during the selection and therefore a inaccurate way to calculate distances is employed if the result is known to be greater than MIN_DISTANCE. @param xa the X coordinate of the first point. @param ya the Y coordinate of the first point. @param xb the X coordinate of the second point. @param yb the Y coordinate of the second point. @return the distance. */ public static double pointToPoint(double xa, double ya, double xb, double yb) { if(Math.abs(xa-xb) < MIN_DISTANCE || Math.abs(ya-yb) < MIN_DISTANCE) return Math.sqrt((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb)); else return MIN_DISTANCE; } /** Calculate the euclidean distance between two points. The distance calculated here is not accurate. It is meant only to discriminate objects during the selection and therefore a inaccurate way to calculate distances is employed if the result is known to be greater than MIN_DISTANCE. @param xa the X coordinate of the first point. @param ya the Y coordinate of the first point. @param xb the X coordinate of the second point. @param yb the Y coordinate of the second point. @return the distance. */ public static int pointToPoint(int xa, int ya, int xb, int yb) { if(Math.abs(xa-xb) < MIN_DISTANCE || Math.abs(ya-yb) < MIN_DISTANCE) return (int)Math.sqrt((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb)); else return MIN_DISTANCE; } /** Calculate the euclidean distance between a point and a segment. Adapted from http://www.vb-helper.com/howto_distance_point_to_line.html The distance calculated here is not accurate. It is meant only to discriminate objects during the selection and therefore a inaccurate way to calculate distances is employed if the result is known to be greater than MIN_DISTANCE. @param xa the X coordinate of the starting point of the segment. @param ya the Y coordinate of the starting point of the segment. @param xb the X coordinate of the ending point of the segment. @param yb the Y coordinate of the ending point of the segment. @param x the X coordinate of the point. @param y the Y coordinate of the point. @return the calculated distance. */ public static double pointToSegment(double xa, double ya, double xb, double yb, double x, double y) { // Shortcuts if(xa>xb) { xmin = xb; xmax = xa; } else { xmin = xa; xmax = xb; } if(x<xmin-MIN_DISTANCE || x>xmax+MIN_DISTANCE) return MIN_DISTANCE; if(ya>yb) { ymin = yb; ymax = ya; } else { ymin = ya; ymax = yb; } if(y<ymin-MIN_DISTANCE || y>ymax+MIN_DISTANCE) return MIN_DISTANCE; dx=xb-xa; dy=yb-ya; if (dx==0 && dy==0) { dx=x-xa; dy=y-yb; return Math.sqrt(dx*dx+dy*dy); } t=((x-xa)*dx+(y-ya)*dy)/(dx*dx+dy*dy); if (t<0.0) { dx=x-xa; dy=y-ya; } else if (t>1.0){ dx=x-xb; dy=y-yb; } else { dx=x-(xa+t*dx); dy=y-(ya+t*dy); } return Math.sqrt(dx*dx+dy*dy); } /** Calculate the euclidean distance between a point and a segment. Adapted from http://www.vb-helper.com/howto_distance_point_to_line.html This is a version which does all calculations in fixed point with three digits and it should be faster than the double precision version on some platforms. @param xa the X coordinate of the starting point of the segment. @param ya the Y coordinate of the starting point of the segment. @param xb the X coordinate of the ending point of the segment. @param yb the Y coordinate of the ending point of the segment. @param x the X coordinate of the point. @param y the Y coordinate of the point. @return the calculated distance. */ public static int pointToSegment(int xa, int ya, int xb, int yb, int x, int y) { // Shortcuts if(xa>xb) { ixmin = xb; ixmax = xa; } else { ixmin = xa; ixmax = xb; } if(x<ixmin-MIN_DISTANCE || x>ixmax+MIN_DISTANCE) return MIN_DISTANCE; if(ya>yb) { iymin = yb; iymax = ya; } else { iymin = ya; iymax = yb; } if(y<iymin-MIN_DISTANCE || y>iymax+MIN_DISTANCE) return MIN_DISTANCE; if (xb==xa && yb==ya) { idx=x-xa; idy=y-yb; return (int)Math.sqrt(idx*idx+idy*idy); } idx=xb-xa; idy=yb-ya; // This is an integer, fixed point implementation. We suppose to make // calculations with three decimals. it=(1000*((x-xa)*idx+(y-ya)*idy))/(idx*idx+idy*idy); if (it<0) { idx=x-xa; idy=y-ya; } else if (it>1000){ idx=x-xb; idy=y-yb; } else { idx=x-(xa+it*idx/1000); // NOPMD parentheses are useful here! idy=y-(ya+it*idy/1000); // NOPMD parentheses are useful here! } return (int)Math.sqrt(idx*idx+idy*idy); } /** Tells if a point lies inside a polygon, using the alternance rule adapted from a snippet by Randolph Franklin, in Paul Bourke pages: http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ @param xp vector of x coordinates of vertices @param yp vector of y coordinates of vertices @param npol number of vertices @param x x coordinate of the point @param y y coordinate of the point @return true if the point lies in the polygon, false otherwise. */ public static boolean pointInPolygon( int[] xp, int[] yp,int npol, double x, double y) { c = false; for (i = 0,j = npol-1; i < npol; j=i++) { if ((yp[i] <= y && y < yp[j] || yp[j] <= y && y < yp[i]) && x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]) c = !c; j=i; } return c; } /** Tells if a point lies inside an ellipse @param ex x coordinate of the top left corner of the ellipse @param ey y coordinate of the top left corner of the ellipse @param w width of the ellipse @param h height of the ellipse @param px x coordinate of the point @param py y coordinate of the point @return true if the point lies in the ellipse, false otherwise. */ public static boolean pointInEllipse(double ex,double ey,double w, double h,double px,double py) { //Determine and normalize quadrant. dx = Math.abs(px-(ex+w/2.0)); // NOPMD dy = Math.abs(py-(ey+h/2.0)); // NOPMD //Shortcut if( dx > w/2.0 || dy > h/2.0) { return false; } // The multiplication by four is mandatory as the principal axis of an // ellipse are the half of the width and the height. return (4.0*dx*dx/w/w+4.0*dy*dy/h/h)<1.0; } /** Tells if a point lies inside an ellipse (integer version). @param ex x coordinate of the top left corner of the ellipse. @param ey y coordinate of the top left corner of the ellipse. @param w width of the ellipse. @param h height of the ellipse. @param px x coordinate of the point. @param py y coordinate of the point. @return true if the point lies in the ellipse, false otherwise. */ public static boolean pointInEllipse(int ex,int ey,int w, int h, int px,int py) { return pointInEllipse((double) ex,(double) ey,(double) w, (double) h,(double) px,(double) py); /* // On my iMac G5 the integer code is SLOWER than the double precision // calculations. //Determine and normalize quadrant. idx = Math.abs(px-(ex+w/2)); idy = Math.abs(py-(ey+h/2)); //Shortcut if( idx > w/2 || idy > h/2 ) { return false; } // Calculate the semi-latus rectum of the ellipse at the given point // The multiplication by four is mandatory as the principal axis of an // ellipse are the half of the width and the height. // Integer calculation with three digit accuracy. return (4000*idx*idx/w/w+4000*dy*dy/h/h)<1000; */ } /** Give the distance between the given point and the ellipse path. The difference with pointInEllipse is that pointInEllipse gives 0 when the point is inside the ellipse, where here we get the distance with the contour of the ellipse. @param ex x coordinate of the top left corner of the ellipse. @param ey y coordinate of the top left corner of the ellipse. @param w width of the ellipse. @param h height of the ellipse. @param px x coordinate of the point. @param py y coordinate of the point. @return the distance to the contour of the ellipse. */ public static double pointToEllipse(double ex,double ey,double w, double h,double px,double py) { // Calculate distance of the point from center of ellipse. double dx = Math.abs(px-(ex+w/2.0)); // NOPMD parentheses are useful! double dy = Math.abs(py-(ey+h/2.0)); // NOPMD parentheses are useful! // Treat separately the degenerate cases. This will avoid a divide // by zero anomalous situation. if (w==0) return pointToSegment(ex, ey, ex, ey+h, px, py); if (h==0) return pointToSegment(ex, ey, ex+w, ey, px, py); // Calculate the semi-latus rectum of the ellipse at the given point double l=(dx*dx/w/w+dy*dy/h/h)*4.0; return (Math.abs(l-1.0))*Math.min(w,h)/4.0; } /** Give the distance between the given point and the ellipse path (integer version). @param ex x coordinate of the top left corner of the ellipse. @param ey y coordinate of the top left corner of the ellipse. @param w width of the ellipse. @param h height of the ellipse. @param px x coordinate of the point. @param py y coordinate of the point. @return the distance to the contour of the ellipse. */ public static int pointToEllipse(int ex,int ey,int w, int h, int px,int py) { return (int)Math.round(pointToEllipse((double) ex,(double)ey,(double) w, (double) h,(double) px,(double) py)); } /** Tells if a point lies inside a rectangle. @param ex x coordinate of the top left corner of the rectangle. @param ey y coordinate of the top left corner of the rectangle. @param w width of the rectangle. @param h height of the rectangle. @param px x coordinate of the point. @param py y coordinate of the point. @return true if the point lies in the ellipse, false otherwise. */ public static boolean pointInRectangle(double ex,double ey,double w, double h,double px,double py) { return !(ex>px||px>ex+w || ey>py || py>ey+h); } /** Tells if a point lies inside a rectangle, integer version. @param ex x coordinate of the top left corner of the rectangle. @param ey y coordinate of the top left corner of the rectangle. @param w width of the rectangle. @param h height of the rectangle. @param px x coordinate of the point. @param py y coordinate of the point. @return true if the point lies in the ellipse, false otherwise. */ public static boolean pointInRectangle(int ex,int ey,int w, int h, int px,int py) { return !(ex>px || px>ex+w || ey>py || py>ey+h); } /** Give the distance between the given point and the borders of a rectangle. @param ex x coordinate of the top left corner of the rectangle. @param ey y coordinate of the top left corner of the rectangle. @param w width of the rectangle. @param h height of the rectangle. @param px x coordinate of the point. @param py y coordinate of the point. @return the distance to one of the border of the rectangle. */ public static double pointToRectangle(double ex,double ey,double w, double h, double px,double py) { double d1=pointToSegment(ex,ey,ex+w,ey,px,py); double d2=pointToSegment(ex+w,ey,ex+w,ey+h,px,py); double d3=pointToSegment(ex+w,ey+h,ex,ey+h,px,py); double d4=pointToSegment(ex,ey+h,ex,ey,px,py); return Math.min(Math.min(d1,d2),Math.min(d3,d4)); } /** Give the distance between the given point and the borders of a rectangle (integer version). @param ex x coordinate of the top left corner of the rectangle. @param ey y coordinate of the top left corner of the rectangle. @param w width of the rectangle. @param h height of the rectangle. @param px x coordinate of the point. @param py y coordinate of the point. @return the distance to one of the border of the rectangle. */ public static int pointToRectangle(int ex,int ey,int w, int h, int px,int py) { int d1=pointToSegment(ex,ey,ex+w,ey,px,py); int d2=pointToSegment(ex+w,ey,ex+w,ey+h,px,py); int d3=pointToSegment(ex+w,ey+h,ex,ey+h,px,py); int d4=pointToSegment(ex,ey+h,ex,ey,px,py); return Math.min(Math.min(d1,d2),Math.min(d3,d4)); } /** Give an approximation of the distance between a point and a Bézier curve. The curve is divided into MAX_BEZIER_SEGMENTS linear pieces and the distance is calculated with each piece. The given distance is the minimum distance found for all pieces. Freely inspired from the original FidoCAD code. @param x1 x coordinate of the first control point of the Bézier curve. @param y1 y coordinate of the first control point of the Bézier curve. @param x2 x coordinate of the second control point of the Bézier curve. @param y2 y coordinate of the second control point of the Bézier curve. @param x3 x coordinate of the third control point of the Bézier curve. @param y3 y coordinate of the third control point of the Bézier curve. @param x4 x coordinate of the fourth control point of the Bézier curve. @param y4 y coordinate of the fourth control point of the Bézier curve. @param px x coordinate of the point. @param py y coordinate of the point. @return an approximate value of the distance between the given point and the Bézier curve specified by the control points. */ public static int pointToBezier(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int px, int py) { int distance=Integer.MAX_VALUE; double b03, b13, b23, b33; double umu; double u; i=0; int[] x=new int[MAX_BEZIER_SEGMENTS+1]; int[] y=new int[MAX_BEZIER_SEGMENTS+1]; double limit=1.0/(double)(MAX_BEZIER_SEGMENTS); // (1+MAX_BEZIER_SEGMENTS/100) is to avoid roundoff for(u = 0; u < (1+MAX_BEZIER_SEGMENTS/100); u += limit) { // This is the parametric form of the Bézier curve. // Probably, this is not the most convenient way to draw the // curve (one should probably use De Casteljau's Algorithm), // but it indeed OK to find a few values such the one we need umu=1-u; b03 = umu*umu*umu; b13 = 3 * u * umu*umu; b23 = 3 * u * u * umu; b33 = u*u*u; x[i] = (int)(x1 * b03 + x2 * b13 + x3 * b23 + x4 * b33); y[i] = (int)(y1 * b03 + y2 * b13 + y3 * b23 + y4 * b33); ++i; } // Calculate the distance of the given point with each of the // obtained segments. for(j=0;j<MAX_BEZIER_SEGMENTS;++j) { distance=Math.min(distance, pointToSegment(x[j], y[j], x[j+1], y[j+1],px, py)); } return distance; } }