package org.gvt.util;
import java.awt.geom.Line2D;
import java.util.*;
import org.eclipse.draw2d.geometry.*;
import org.gvt.util.ChsRectangle;
/**
* This class maintains a list of static geometry related utility methods.
*
* @author Ugur Dogrusoz
* @author Esat Belviranli
*
* Copyright: i-Vis Research Group, Bilkent University, 2007 - present
*/
abstract public class ChsGeometry
{
// -----------------------------------------------------------------------------
// Section: Class Methods
// -----------------------------------------------------------------------------
/**
* This method calculates the intersection (clipping) point the input
* rectangle with the line defined by the input point and input rectangle center.
*/
public static PrecisionPoint getIntersection(ChsRectangle rect, double p2x, double p2y)
{
double p1x = rect.getCenterX();
double p1y = rect.getCenterY();
double topLeftX = rect.x;
double topLeftY = rect.y;
double topRightX = rect.getRight();
double bottomLeftX = rect.x;
double bottomLeftY = rect.getBottom();
double bottomRightX = rect.getRight();
//input point is inside of node
if(p2x >= topLeftX && p2x <= topRightX &&
p2y >= topLeftY && p2y <= bottomLeftY)
{
return null;
}
// line is vertical
if (p1x == p2x)
{
if(p1y > p2y)
{
return new PrecisionPoint(p1x, topLeftY);
}
else if(p1y < p2y)
{
return new PrecisionPoint(p1x, bottomLeftY);
}
else
{
//not line, return null;
}
}
// line is horizontal
else if (p1y == p2y)
{
if(p1x > p2x)
{
return new PrecisionPoint(topLeftX, p1y);
}
else if(p1x < p2x)
{
return new PrecisionPoint(topRightX, p1y);
}
else
{
//not valid line, return null;
}
}
else
{
//slope of node's diagonal
double slope = rect.height / rect.width;
//slope of line between center of rectangle and input point
double slopePrime = (p2y - p1y) / (p2x - p1x);
int cardinalDirection;
double tempPointX;
double tempPointY;
//determine whether clipping pint is corner of node
if(slope == slopePrime)
{
if(p1x > p2x)
{
return new PrecisionPoint(bottomLeftX, bottomLeftY);
}
else
{
return new PrecisionPoint(topRightX, topLeftY);
}
}
else if((-slope) == slopePrime)
{
if(p1x > p2x)
{
return new PrecisionPoint(topLeftX, topLeftY);
}
else
{
return new PrecisionPoint(bottomRightX, bottomLeftY);
}
}
//determine cardinal Direction
if(p1x > p2x)
{
if(p1y > p2y)
{
cardinalDirection = getCardinalDirection(slope, slopePrime, 4);
}
else
{
cardinalDirection = getCardinalDirection(-slope, slopePrime, 3);
}
}
else
{
if(p1y > p2y)
{
cardinalDirection = getCardinalDirection(-slope, slopePrime, 1);
}
else
{
cardinalDirection = getCardinalDirection(slope, slopePrime, 2);
}
}
//find clipping point
switch(cardinalDirection)
{
case 1:
tempPointY = topLeftY;
tempPointX = intersectsAtY(p1x, p2x, p1y, p2y, tempPointY);
if (tempPointX != Double.NaN &&
tempPointX <= bottomRightX &&
tempPointX >= bottomLeftX)
{
return new PrecisionPoint(tempPointX, tempPointY);
}
case 2:
tempPointX = bottomRightX;
tempPointY = intersectsAtX(p1x, p2x,
p1y, p2y, tempPointX);
if (tempPointY != Double.NaN &&
topLeftY < tempPointY &&
tempPointY < bottomLeftY)
{
return new PrecisionPoint(tempPointX, tempPointY);
}
case 3:
tempPointY = bottomLeftY;
tempPointX = intersectsAtY(p1x, p2x, p1y, p2y, tempPointY);
if (tempPointX != Double.NaN &&
tempPointX <= bottomRightX &&
tempPointX >= bottomLeftX)
{
return new PrecisionPoint(tempPointX, tempPointY);
}
case 4:
tempPointX = bottomLeftX;
tempPointY = intersectsAtX(p1x, p2x,
p1y, p2y, tempPointX);
if (tempPointY != Double.NaN &&
topLeftY < tempPointY &&
tempPointY < bottomLeftY)
{
return new PrecisionPoint(tempPointX, tempPointY);
}
}
}
return null;
}
/**
* This method returns in which cardinal direction does input point stays
* 1: North
* 2: East
* 3: South
* 4: West
*/
private static int getCardinalDirection(double slope, double slopePrime, int line)
{
if(slope > slopePrime)
{
return line;
}
else
{
return 1 + line % 4;
}
}
/**
* This method calculates the intersection (clipping) point the input
* rectangle with the line defined by the input point pair.
*/
public static PrecisionPoint getIntersection(ChsRectangle rect,
double p1x, double p1y,
double p2x, double p2y)
{
double ip1x = 0;
double ip1y = 0;
double ip2x = 0;
double ip2y = 0;
// Since we are not using objects for performance constraints,
// we should have booleans indicating whether we found some
// intersection point.
boolean ip1Null = true;
boolean ip2Null = true;
double topLeftX = rect.x;
double topLeftY = rect.y;
double topRightX = rect.getRight();
double bottomLeftX = rect.x;
double bottomLeftY = rect.getBottom();
double bottomRightX = rect.getRight();
// Calculate top line
if (p1x == p2x)
{
if (p1y == p2y)
{
// not a valid line, return null
}
else if (p1x >= bottomLeftX &&
p1x <= bottomRightX)
{
// line vertical
if (topLeftY != bottomLeftY)
{
ip1x = p1x;
ip1y = bottomLeftY;
ip2x = p1x;
ip2y = topLeftY;
ip1Null = ip2Null = false;
}
else
{
// Rectangle is in fact a line
ip1x = p1x;
ip1y = topLeftY;
ip1Null = false;
}
}
}
else if (p1y == p2y)
{
// line horizantal
if (p1y >= bottomLeftY &&
p1y <= topLeftY)
{
if (topLeftX != topRightX)
{
ip1x = topRightX;
ip1y = p1y;
ip2x = p1x;
ip2y = topLeftY;
ip1Null = ip2Null = false;
}
else
{
// Rectangle is in fact a line
ip1x = topLeftX;
ip1y = p1y;
ip1Null = false;
}
}
}
else
{
double tempPointX;
double tempPointY;
// General case, the line is not vertical or horizantal
// Test hitting the top of the ractangle
tempPointY = topLeftY;
tempPointX = intersectsAtY(p1x, p2x,
p1y, p2y, tempPointY);
if (tempPointX != Double.NaN &&
tempPointX <= bottomRightX &&
tempPointX >= bottomLeftX)
{
ip1x = tempPointX;
ip1y = topLeftY;
ip1Null = false;
}
// Test hitting the bottom of the ractangle
tempPointY = bottomLeftY;
tempPointX = intersectsAtY(p1x, p2x,
p1y, p2y, tempPointY);
if (tempPointX != Double.NaN &&
tempPointX <= bottomRightX &&
tempPointX >= bottomLeftX)
{
if (ip1Null)
{
ip1x = tempPointX;
ip1y = bottomLeftY;
ip1Null = false;
}
else
{
ip2x = tempPointX;
ip2y = bottomLeftY;
ip2Null = false;
}
}
// When hitting left or right, we exclude the corner points,
// such that we don't get them twice.
// Test hitting left of rectangle
tempPointX = bottomLeftX;
tempPointY = intersectsAtX(p1x, p2x,
p1y, p2y, tempPointX);
if (tempPointY != Double.NaN &&
topLeftY < tempPointY &&
tempPointY < bottomLeftY)
{
if (ip1Null)
{
ip1x = bottomLeftX;
ip1y = tempPointY;
ip1Null = false;
}
else
{
ip2x = bottomLeftX;
ip2y = tempPointY;
ip2Null = false;
}
}
// Test hitting right of rectangle
tempPointX = bottomRightX;
tempPointY = intersectsAtX(p1x, p2x,
p1y, p2y, tempPointX);
if (tempPointY != Double.NaN &&
topLeftY < tempPointY &&
tempPointY < bottomLeftY)
{
if (ip1Null)
{
ip1x = bottomRightX;
ip1y = tempPointY;
ip1Null = false;
}
else
{
ip2x = bottomRightX;
ip2y = tempPointY;
ip2Null = false;
}
}
}
PrecisionPoint ip1 = null;
PrecisionPoint ip2 = null;
if (!ip1Null)
{
ip1 = new PrecisionPoint(ip1x,ip1y);
}
if (!ip2Null)
{
ip2 = new PrecisionPoint(ip2x,ip2y);
}
return findTheClosestPoint(p2x, p2y, ip1, ip2);
}
/**
* This method returns the closest point, among the first and seconds point,
* to the reference point
*/
private static PrecisionPoint findTheClosestPoint(double refPointX,
double refPointY,
PrecisionPoint first,
PrecisionPoint second)
{
PrecisionPoint temp = null;
if (second == null && first != null)
{
temp = first;
}
else if (second != null && first == null)
{
temp = second;
}
else if (second != null && first != null)
{
double term1 = refPointX - first.preciseX;
double term2 = refPointY - first.preciseY;
double distanceFirst = term1 * term1 + term2 * term2;
term1 = refPointX - second.preciseX;
term2 = refPointY - second.preciseY;
double distanceSecond = term1 * term1 + term2 * term2;
if (distanceFirst >= distanceSecond)
{
temp = second;
}
else
{
temp = first;
}
}
return temp;
}
/**
* This method finds the y coordinate of a line at a specified x coordinate
* returns a valid point if the line exists at x point and the line is not
* vertical.
*/
private static double intersectsAtX(double x1, double x2,
double y1, double y2, double x)
{
double y = Double.NaN;
if (x1 != x2 )
{
if (x == x1)
{
y = y1;
}
else if (x == x2)
{
y = y2;
}
else
{
double tempY = y1 + ((y2 - y1) * (x - x1)) / (x2 - x1);
// Check whether found y is lying on the rectangle edge.
if (((tempY <= y1 && tempY >= y2)||
(tempY >= y1 && tempY <= y2)))
{
y = tempY;
}
}
}
return y;
}
/**
* This method finds the x coordinate of a line at a specified y coordinate
* returns a valid point if the line exists at y point and the line is not
* horizantal.
*/
private static double intersectsAtY(double x1, double x2,
double y1, double y2, double y)
{
double x = Double.NaN;
if (y1 != y2 )
{
if (y == y1)
{
x = x1;
}
else if (y == y2)
{
x = x2;
}
else
{
double tempX = x1 + ((x2 - x1) * (y - y1)) / (y2 - y1);
// Check whether found x is lying on the rectangle edge.
if (((tempX <= x1 && tempX >= x2)||
(tempX >= x1 && tempX <= x2)))
{
x = tempX;
}
}
}
return x;
}
/**
* This method calculates the intersection of the two lines defined by
* point pairs (s1,s2) and (f1,f2).
*/
public static Point getIntersection(Point s1, Point s2, Point f1, Point f2)
{
int x1 = s1.x;
int y1 = s1.y;
int x2 = s2.x;
int y2 = s2.y;
int x3 = f1.x;
int y3 = f1.y;
int x4 = f2.x;
int y4 = f2.y;
int x, y; // intersection point
int a1, a2, b1, b2, c1, c2; // coefficients of line eqns.
int denom;
a1 = y2 - y1;
b1 = x1 - x2;
c1 = x2 * y1 - x1 * y2; // { a1*x + b1*y + c1 = 0 is line 1 }
a2 = y4 - y3;
b2 = x3 - x4;
c2 = x4 * y3 - x3 * y4; // { a2*x + b2*y + c2 = 0 is line 2 }
denom = a1 * b2 - a2 * b1;
if (denom == 0)
{
return null;
}
x = (b1 * c2 - b2 * c1) / denom;
y = (a2 * c1 - a1 * c2) / denom;
return new Point(x, y);
}
/**
* This method finds and returns the angle of the vector from the + x-axis
* in clockwise direction (compatible w/ Java coordinate system!).
*/
public static double angleOfVector(double Cx, double Cy,
double Nx, double Ny)
{
double C_angle;
if (Cx != Nx)
{
C_angle = Math.atan((Ny - Cy) / (Nx - Cx));
if (Nx < Cx)
{
C_angle += Math.PI;
}
else if (Ny < Cy)
{
C_angle += TWO_PI;
}
}
else if (Ny < Cy)
{
C_angle = ONE_AND_HALF_PI; // 270 degrees
}
else
{
C_angle = HALF_PI; // 90 degrees
}
// assert 0.0 <= C_angle && C_angle < TWO_PI;
return C_angle;
}
/**
* This method converts the given angle in radians to degrees.
*/
public static double radian2degree(double rad)
{
return 180.0 * rad / Math.PI;
}
/**
* This method checks whether the given two line segments (one with point
* p1 and p2, the other with point p3 and p4) intersect at a point other
* than these points.
*/
public static boolean doIntersect(PrecisionPoint p1, PrecisionPoint p2,
PrecisionPoint p3, PrecisionPoint p4)
{
boolean result = Line2D.linesIntersect(p1.preciseX, p1.preciseY,
p2.preciseX, p2.preciseY, p3.preciseX, p3.preciseY,
p4.preciseX, p4.preciseY);
return result;
}
/*
* Main method for testing purposes.
*/
public static void main(String [] args)
{
ChsRectangle rect = new ChsRectangle();
PrecisionPoint p1;
PrecisionPoint p2;
Random rand = new Random();
double x;
double y;
double width;
double height;
double p1x;
double p1y;
double p2x;
double p2y;
int counter = 0;
int i,j=0;
for(i=0; i<10; i++)
{
System.out.println("------------------------------------");
System.out.println("Test " + i + ":");
x = 1000 * rand.nextDouble();
y = 1000 * rand.nextDouble();
width = 1000 * rand.nextDouble();
height = 1000 * rand.nextDouble();
System.out.println("X: " + x + " Y: " + y + " Width: " + width + " Height: " + height);
rect.setX(x);
rect.setY(y);
rect.setWidth(width);
rect.setHeight(height);
p1x = rect.getCenterX();
p1y = rect.getCenterY();
p2x = 1000 * rand.nextDouble();
p2y = 1000 * rand.nextDouble();
System.out.println("Input Point: X - " + p2x + " Y - " + p2y);
p2 = ChsGeometry.getIntersection(rect, p2x, p2y);
p1 = ChsGeometry.getIntersection(rect, p1x, p1y, p2x, p2y);
if(p1 == null && p2 == null)
{
i--;
System.out.println("No clipping point");
continue;
}
else if(p2 == null)
{
i--;j++;
System.out.println("p2 is NULL");
continue;
}
else if(p1 == null)
{
i--;j++;
System.out.println("p1 is NULL");
continue;
}
System.out.println("New: X - " + p2.preciseX + " Y - " + p2.preciseY);
System.out.println("Old: X - " + p1.preciseX + " Y - " + p1.preciseY);
if((Math.abs(p1.preciseX - p2.preciseX) < 0.000000001) && (Math.abs(p1.preciseY - p2.preciseY) < 0.00000001))
counter++;
}
System.out.println(counter + " tests out of " + i + " tests are correct!");
System.out.println(j + " tests out of " + (i+j) + " tests are skipped!");
}
// -----------------------------------------------------------------------------
// Section: Class Constants
// -----------------------------------------------------------------------------
/**
* Some useful pre-calculated constants
*/
public static final double HALF_PI = 0.5 * Math.PI;
public static final double ONE_AND_HALF_PI = 1.5 * Math.PI;
public static final double TWO_PI = 2.0 * Math.PI;
public static final double THREE_PI = 3.0 * Math.PI;
}