package research.util;
/*
* @(#)Geom.java
*
* Project: JHotdraw - a GUI framework for technical drawings
* http://www.jhotdraw.org
* http://jhotdraw.sourceforge.net
* Copyright: ? by the original author(s) and all contributors
* License: Lesser GNU Public License (LGPL)
* http://www.opensource.org/licenses/lgpl-license.html
*/
import java.awt.Rectangle;
import java.awt.Point;
/**
* Some geometric utilities.
*
* @version <$CURRENT_VERSION$>
*/
public class Geom {
private Geom() {} // never instantiated
/**
* Tests if a point is on a line.
*/
static public boolean lineContainsPoint(int x1, int y1,
int x2, int y2,
int px, int py) {
Rectangle r = new Rectangle(new Point(x1, y1));
r.add(x2, y2);
r.grow(2, 2);
if (! r.contains(px,py)) {
return false;
}
double a, b, x, y;
if (x1 == x2) {
return (Math.abs(px - x1) < 3);
}
if (y1 == y2) {
return (Math.abs(py - y1) < 3);
}
a = (double)(y1 - y2) / (double)(x1 - x2);
b = (double)y1 - a * (double)x1;
x = (py - b) / a;
y = a * px + b;
return (Math.min(Math.abs(x - px), Math.abs(y - py)) < 4);
}
static public final int NORTH = 1;
static public final int SOUTH = 2;
static public final int WEST = 3;
static public final int EAST = 4;
/**
* Returns the direction NORTH, SOUTH, WEST, EAST from
* one point to another one.
*/
static public int direction(int x1, int y1, int x2, int y2) {
int direction = 0;
int vx = x2 - x1;
int vy = y2 - y1;
if (vy < vx && vx > -vy) {
direction = EAST;
}
else if (vy > vx && vy > -vx) {
direction = NORTH;
}
else if (vx < vy && vx < -vy) {
direction = WEST;
}
else {
direction = SOUTH;
}
return direction;
}
static public Point center(Rectangle r) {
return new Point(r.x + r.width /2, r.y + r.height/2);
}
static public Point north(Rectangle r) {
return new Point(r.x+r.width/2, r.y);
}
static public Point south(Rectangle r) {
return new Point(r.x + r.width /2, r.y + r.height);
}
static public Point west(Rectangle r) {
return new Point(r.x, r.y + r.height/ 2);
}
static public Point east(Rectangle r) {
return new Point(r.x+r.width, r.y + r.height/ 2);
}
static public Point northWest(Rectangle r) {
return new Point(r.x, r.y);
}
static public Point northEast(Rectangle r) {
return new Point(r.x+r.width, r.y);
}
static public Point southWest(Rectangle r) {
return new Point(r.x, r.y + r.height);
}
static public Point southEast(Rectangle r) {
return new Point(r.x + r.width, r.y + r.height);
}
/**
* Constains a value to the given range.
* @return the constrained value
*/
static public int range(int min, int max, int value) {
if (value < min) {
value = min;
}
if (value > max) {
value = max;
}
return value;
}
/**
* Gets the square distance between two points.
*/
static public long length2(int x1, int y1, int x2, int y2) {
return (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
}
/**
* Gets the distance between to points
*/
static public long length(int x1, int y1, int x2, int y2) {
return (long)Math.sqrt(length2(x1, y1, x2, y2));
}
/**
* Gets the angle of a point relative to a rectangle.
*/
static public double pointToAngle(Rectangle r, Point p) {
int px = p.x - (r.x + r.width/2);
int py = p.y - (r.y + r.height/2);
return Math.atan2(py*r.width, px*r.height);
}
/**
* Gets the point on a rectangle that corresponds to the given angle.
*/
static public Point angleToPoint(Rectangle r, double angle) {
double si = Math.sin(angle);
double co = Math.cos(angle);
double e = 0.0001;
int x= 0, y= 0;
if (Math.abs(si) > e) {
x= (int) ((1.0 + co/Math.abs(si))/2.0 * r.width);
x= range(0, r.width, x);
}
else if (co >= 0.0) {
x= r.width;
}
if (Math.abs(co) > e) {
y= (int) ((1.0 + si/Math.abs(co))/2.0 * r.height);
y= range(0, r.height, y);
}
else if (si >= 0.0) {
y= r.height;
}
return new Point(r.x + x, r.y + y);
}
/**
* Converts a polar to a point
*/
static public Point polarToPoint(double angle, double fx, double fy) {
double si = Math.sin(angle);
double co = Math.cos(angle);
return new Point((int)(fx*co+0.5), (int)(fy*si+0.5));
}
/**
* Gets the point on an oval that corresponds to the given angle.
*/
static public Point ovalAngleToPoint(Rectangle r, double angle) {
Point center = Geom.center(r);
Point p = Geom.polarToPoint(angle, r.width/2, r.height/2);
return new Point(center.x + p.x, center.y + p.y);
}
/**
* Standard line intersection algorithm
* Return the point of intersection if it exists, else null
**/
// from Doug Lea's PolygonFigure
static public Point intersect(int xa, // line 1 point 1 x
int ya, // line 1 point 1 y
int xb, // line 1 point 2 x
int yb, // line 1 point 2 y
int xc, // line 2 point 1 x
int yc, // line 2 point 1 y
int xd, // line 2 point 2 x
int yd) { // line 2 point 2 y
// source: http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html
// eq: for lines AB and CD
// (YA-YC)(XD-XC)-(XA-XC)(YD-YC)
// r = ----------------------------- (eqn 1)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = ----------------------------- (eqn 2)
// (XB-XA)(YD-YC)-(YB-YA)(XD-XC)
// XI = XA + r(XB-XA)
// YI = YA + r(YB-YA)
double denom = ((xb - xa) * (yd - yc) - (yb - ya) * (xd - xc));
double rnum = ((ya - yc) * (xd - xc) - (xa - xc) * (yd - yc));
if (denom == 0.0) { // parallel
if (rnum == 0.0) { // coincident; pick one end of first line
if ((xa < xb && (xb < xc || xb < xd)) ||
(xa > xb && (xb > xc || xb > xd))) {
return new Point(xb, yb);
}
else {
return new Point(xa, ya);
}
}
else {
return null;
}
}
double r = rnum / denom;
double snum = ((ya - yc) * (xb - xa) - (xa - xc) * (yb - ya));
double s = snum / denom;
if (0.0 <= r && r <= 1.0 && 0.0 <= s && s <= 1.0) {
int px = (int)(xa + (xb - xa) * r);
int py = (int)(ya + (yb - ya) * r);
return new Point(px, py);
}
else {
return null;
}
}
/**
* compute distance of point from line segment, or
* Double.MAX_VALUE if perpendicular projection is outside segment; or
* If pts on line are same, return distance from point
**/
// from Doug Lea's PolygonFigure
public static double distanceFromLine(int xa, int ya,
int xb, int yb,
int xc, int yc) {
// source:http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html#q7
//Let the point be C (XC,YC) and the line be AB (XA,YA) to (XB,YB).
//The length of the
// line segment AB is L:
//
// ___________________
// | 2 2
// L = \| (XB-XA) + (YB-YA)
//and
//
// (YA-YC)(YA-YB)-(XA-XC)(XB-XA)
// r = -----------------------------
// L**2
//
// (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
// s = -----------------------------
// L**2
//
// Let I be the point of perpendicular projection of C onto AB, the
//
// XI=XA+r(XB-XA)
// YI=YA+r(YB-YA)
//
// Distance from A to I = r*L
// Distance from C to I = s*L
//
// If r < 0 I is on backward extension of AB
// If r>1 I is on ahead extension of AB
// If 0<=r<=1 I is on AB
//
// If s < 0 C is left of AB (you can just check the numerator)
// If s>0 C is right of AB
// If s=0 C is on AB
int xdiff = xb - xa;
int ydiff = yb - ya;
long l2 = xdiff * xdiff + ydiff * ydiff;
if (l2 == 0) {
return Geom.length(xa, ya, xc, yc);
}
double rnum = (ya - yc) * (ya - yb) - (xa - xc) * (xb - xa);
double r = rnum / l2;
if (r < 0.0 || r > 1.0) {
return Double.MAX_VALUE;
}
double xi = xa + r * xdiff;
double yi = ya + r * ydiff;
double xd = xc - xi;
double yd = yc - yi;
return Math.sqrt(xd * xd + yd * yd);
/*
for directional version, instead use
double snum = (ya-yc) * (xb-xa) - (xa-xc) * (yb-ya);
double s = snum / l2;
double l = Math.sqrt((double)l2);
return = s * l;
*/
}
}