// Segment.java // by Scot Drysdale on 5/17/06 // Holds the data on a segment, consisting of two endpoints. import java.awt.*; // needed for the drawing code import java.awt.geom.Point2D; public class Segment extends Shape { private PointShape endpt1, endpt2; // The endpoints private static double TOLERANCE = 0.0000001; // Tolerance for error public Segment(Point p1, Point p2, Color c) { super(c); endpt1 = new PointShape(p1, c); endpt2 = new PointShape(p2, c); } public Segment(Point p1, Point p2) { endpt1 = new PointShape(p1); endpt2 = new PointShape(p2); } public Segment(PointShape p1, PointShape p2) { endpt1 = p1; endpt2 = p2; } // return first endpoint given. public PointShape getEndpoint1() { return endpt1; } // return second endpoint given. public PointShape getEndpoint2() { return endpt2; } // Set the second endpoint to p public void setEndpoint2(Point2D p) { endpt2 = new PointShape(p); } // Draw the segment public void draw(Graphics page) { page.drawLine((int) (endpt1.getX() + 0.5), (int) (endpt1.getY() + 0.5), (int) (endpt2.getX() + 0.5), (int) (endpt2.getY() + 0.5)); endpt1.draw(page); endpt2.draw(page); } // returns the distance squared from point (px, py) to this segment public double euclideanSq(double px, double py) { double x, y; // (x,y) is closest point on line to p double x1 = endpt1.getX(); double x2 = endpt2.getX(); double y1 = endpt1.getY(); double y2 = endpt2.getY(); // Find the closest (x,y) to p on the line through the endpoints if (Math.abs(x1 - x2) < TOLERANCE) // vertical segment? { x = x1; y = py; } else if (Math.abs(y1 - y2) < TOLERANCE) // horizontal segment? { y = y1; x = px; } else { // Here, we know that the segment is neither vertical nor horizontal. // Compute m, the slope of the line containing the segment. double m = (y1 - y2) / (x1 - x2); // Compute mperp, the slope of the line perpendicular to the segment. double mperp = -1.0 / m; // Compute the (x, y) intersection of the line containing the segment // and the line that is perpendicular to the segment and that contains // Point p. x = (y1 - py - (m * x1) + (mperp * px)) / (mperp - m); y = m * (x - x1) + y1; } // Does (x,y) lies between (x1, y1) and (x2, y2) on the segment? if(Math.abs(Math.abs(x - x1) + Math.abs(x - x2) - Math.abs(x1 - x2)) < TOLERANCE && Math.abs(Math.abs(y - y1) + Math.abs(y - y2) - Math.abs(y1 - y2)) < TOLERANCE) return Point2D.distanceSq(x, y, px, py); else return Math.min(endpt1.euclideanSq(px, py), endpt2.euclideanSq(px, py)); } // returns the L_1 distance from point (x, y) to this segment public double lOne(double x, double y) { double xv, yv; // (xv,yv) is point on line vertical from (x, y) double xh, yh; // (xh,yh) is point on line horizontal from (x, y) double minDist; // minimum distance found so far double x1 = endpt1.getX(); double x2 = endpt2.getX(); double y1 = endpt1.getY(); double y2 = endpt2.getY(); // First guess for distance is distance to nearer endpoint minDist = Math.min(endpt1.lOne(x, y), endpt2.lOne(x,y)); // Find the nearest points on line (vertical and horizontal from (x,y)) // and update minDist, if necessary if (Math.abs(x1 - x2) < TOLERANCE) // vertical segment? { xh = x1; yh = y; // Is point on segment? if(Math.abs(Math.abs(yh - y1) + Math.abs(yh - y2) - Math.abs(y1 - y2)) < TOLERANCE) minDist = Math.min(minDist, PointShape.lOne(x, y, xh, yh)); } else if (Math.abs(y1 - y2) < TOLERANCE) // horizontal segment? { yv = y1; xv = x; // Is point on segment? if(Math.abs(Math.abs(xv - x1) + Math.abs(xv - x2) - Math.abs(x1 - x2)) < TOLERANCE) minDist = Math.min(minDist, PointShape.lOne(x, y, xv, yv)); } else { // Here, we know that the segment is neither vertical nor horizontal. // Compute m, the slope of the line containing the segment. double m = (y1 - y2) / (x1 - x2); xv = x; yv = m*(x-x1) + y1; // Is point on segment? if(Math.abs(Math.abs(xv - x1) + Math.abs(xv - x2) - Math.abs(x1 - x2)) < TOLERANCE) minDist = Math.min(minDist, PointShape.lOne(x, y, xv, yv)); yh = y; xh = (y - y1)/m + x1; // Is point on segment? if(Math.abs(Math.abs(yh - y1) + Math.abs(yh - y2) - Math.abs(y1 - y2)) < TOLERANCE) minDist = Math.min(minDist, PointShape.lOne(x, y, xh, yh)); } return minDist; } // Computes the reciprocal of the aspect-ratio distance to point (x,y). // (This avoids problems with infinity, but reverses normal tests) public double aspectRatioRecip(double x, double y) { double d1 = endpt1.euclideanSq(x, y); double d2 = endpt2.euclideanSq(x, y); double d3 = endpt2.euclideanSq(endpt1.getPoint()); double max = Math.max(d1, Math.max(d2, d3)); double min = Math.min(d1, Math.min(d2, d3)); if (max == 0.0) return 0.0; // 0/0 we define for our purposes to be 0. else return min/max; } // Computes the cosine of the angle between the given point (x, y) and // the endpoints of this segment public double findCosOfAngle(double x, double y) { Point2D v1, v2; // Hold the ends of the vectors v1 = new Point2D.Double(endpt1.getX() - x, endpt1.getY() - y); v2 = new Point2D.Double(endpt2.getX() - x, endpt2.getY() - y); // Compute cosine by dividing the dot product by the product of the // vector lengths return (v1.getX()*v2.getX() + v1.getY()*v2.getY())/ (v1.distance(0.0, 0.0)*v2.distance(0.0, 0.0)); } // Have the Segment move itself. Moves either the whole segment or a // single endpoint, depending on whether the clickpoint is closer to an // endpoint or the center of the segment. public void move(double deltaX, double deltaY, Point2D clickPoint) { double centerDist = getCenter().euclideanSq(clickPoint); if(endpt1.euclideanSq(clickPoint) < centerDist) endpt1.move(deltaX, deltaY, clickPoint); else if(endpt2.euclideanSq(clickPoint) < centerDist) endpt2.move(deltaX, deltaY, clickPoint); else { endpt1.move(deltaX, deltaY, clickPoint); endpt2.move(deltaX, deltaY, clickPoint); } } // Rotate the point by 45 degrees, scaling by sqrt(2) public Shape rotate() { return new Segment((PointShape) endpt1.rotate(), (PointShape) endpt2.rotate()); } // Return the Segment's center. public PointShape getCenter() { return new PointShape(new Point2D.Double((endpt1.getX() + endpt2.getX()) / 2, (endpt1.getY() + endpt2.getY()) / 2)); } // Returns true if the shape is entirely contained in the rectangle public boolean isContained(Rect r) { return r.containsPoint(endpt1) && r.containsPoint(endpt2); } }