/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: PolyBase.java * * Copyright (c) 2003 Sun Microsystems and Static Free Software * * Electric(tm) 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. * * Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.database.geometry; import com.sun.electric.database.hierarchy.Export; import com.sun.electric.database.prototype.PortOriginal; import com.sun.electric.database.prototype.PortProto; import com.sun.electric.database.topology.NodeInst; import com.sun.electric.database.topology.PortInst; import com.sun.electric.database.variable.EditWindow0; import com.sun.electric.database.variable.ElectricObject; import com.sun.electric.database.variable.TextDescriptor; import com.sun.electric.technology.Layer; import com.sun.electric.tool.Job; import java.awt.Rectangle; import java.awt.Shape; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Stack; /** * The Poly class describes an extended set of points * that can be outlines, filled shapes, curves, text, and more. * The Poly also contains a Layer and some connectivity information. */ public class PolyBase implements Shape, PolyNodeMerge { private static final boolean ALLOWTINYPOLYGONS = false; /** the style (outline, text, lines, etc.) */ private Poly.Type style; /** the points */ protected Point2D points[]; /** the layer (used for graphics) */ private Layer layer; /** the bounds of the points */ protected Rectangle2D bounds; /** the PortProto (if from a node or TEXT) */ private PortProto pp; /** the bit saying if the polygon is perfect rectangle */ private char bitRectangle = 2; /** 2 not calculated, 0 not a rectangle, 1 a rectangle */ /** represents X axis */ public static final int X = 0; /** represents Y axis */ public static final int Y = 1; /** represents Z axis */ public static final int Z = 2; /** represents on the plane XY */ public static final int XY = 4; /** * The constructor creates a new Poly given an array of points. * @param points the array of coordinates. */ public PolyBase(Point2D [] points) { initialize(points); } /** * The constructor creates a new Poly that describes a rectangle. * @param cX the center X coordinate of the rectangle. * @param cY the center Y coordinate of the rectangle. * @param width the width of the rectangle. * @param height the height of the rectangle. */ public PolyBase(double cX, double cY, double width, double height) { double halfWidth = width / 2; double halfHeight = height / 2; initialize(makePoints(cX-halfWidth, cX+halfWidth,cY-halfHeight , cY+halfHeight)); } /** * The constructor creates a new Poly that describes a rectangle. * @param rect the Rectangle2D of the rectangle. */ public PolyBase(Rectangle2D rect) { initialize(makePoints(rect)); } /** * Method to create an array of Points that describes a Rectangle. * @param lX the low X coordinate of the rectangle. * @param hX the high X coordinate of the rectangle. * @param lY the low Y coordinate of the rectangle. * @param hY the high Y coordinate of the rectangle. * @return an array of 4 Points that describes the Rectangle. */ public static Point2D [] makePoints(double lX, double hX, double lY, double hY) { return new Point2D.Double[] { new Point2D.Double(lX, lY), new Point2D.Double(hX, lY), new Point2D.Double(hX, hY), new Point2D.Double(lX, hY)}; } /** * Method to create an array of Points that describes a Rectangle. * @param rect the Rectangle. * @return an array of 4 Points that describes the Rectangle. */ public static Point2D [] makePoints(Rectangle2D rect) { double lX = rect.getMinX(); double hX = rect.getMaxX(); double lY = rect.getMinY(); double hY = rect.getMaxY(); return new Point2D.Double[] { new Point2D.Double(lX, lY), new Point2D.Double(hX, lY), new Point2D.Double(hX, hY), new Point2D.Double(lX, hY)}; } /** * Method to help initialize this Poly. */ private void initialize(Point2D [] points) { this.style = Poly.Type.CLOSED; this.points = points; this.layer = null; this.bounds = null; this.pp = null; } /** * Convert to useful string * @return a string describing this object */ public String toString() { StringBuffer buf = new StringBuffer(); if (layer != null) buf.append(layer.getName()+": "); for (Point2D p : points) buf.append("("+p.getX()+", "+p.getY()+"), "); if (style != null) buf.append(style.toString()); return buf.toString(); } /** * Method to return the style associated with this Poly. * The style controls how the points are interpreted (FILLED, CIRCLE, etc.) * @return the style associated with this Poly. */ public Poly.Type getStyle() { return style; } /** * Method to set the style associated with this Poly. * The style controls how the points are interpreted (FILLED, CIRCLE, etc.) * @param style the style associated with this Poly. */ public void setStyle(Poly.Type style) { this.style = style; } /** * Method to return the points associated with this Poly. * @return the points associated with this Poly. */ public Point2D [] getPoints() { return points; } /** * Method to return the layer associated with this Poly. * @return the layer associated with this Poly. */ public Layer getLayer() { return layer != null ? layer.getNonPseudoLayer() : layer; } /** * Method to return the layer or pseudo-layer associated with this Poly. * @return the layer or pseudo-layer associated with this Poly. */ public Layer getLayerOrPseudoLayer() { return layer; } /** * Method to set the layer associated with this Poly. * @param layer the layer associated with this Poly. */ public void setLayer(Layer layer) { this.layer = layer; } /** * Method to tell if the layer associated with this Poly is a pseudo-layer. * @return true if the layer associated with this Poly is a pseudo-layer. */ public boolean isPseudoLayer() { return layer != null && layer.isPseudoLayer(); } /** * Method to return the PortProto associated with this Poly. * This applies to ports on Nodes and Exports on Cells. * @return the PortProto associated with this Poly. */ public PortProto getPort() { return pp; } /** * Method to set the PortProto associated with this Poly. * This applies to ports on Nodes and Exports on Cells. * @param pp the PortProto associated with this Poly. */ public void setPort(PortProto pp) { this.pp = pp; } /** * Method to transformed the points in this Poly. * @param af transformation to apply. */ public void transform(AffineTransform af) { // Nothing to do if (af.getType() == AffineTransform.TYPE_IDENTITY) return; // special case for Poly type CIRCLEARC and THICKCIRCLEARC: if transposing, reverse points if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) { double det = af.getDeterminant(); if (det < 0) for(int i=0; i<points.length; i += 3) { double x = points[i+1].getX(); double y = points[i+1].getY(); points[i+1].setLocation(points[i+2].getX(), points[i+2].getY()); points[i+2].setLocation(x, y); } } af.transform(points, 0, points, 0, points.length); bounds = null; } /** * Method to convert points from lambda units to grid units. */ public void lambdaToGrid() { for (Point2D p: points) p.setLocation(DBMath.lambdaToGrid(p.getX()), DBMath.lambdaToGrid(p.getY())); bounds = null; } /** * Method to convert points from grid units to lambda units. */ public void gridToLambda() { for (Point2D p: points) p.setLocation(DBMath.gridToLambda(p.getX()), DBMath.gridToLambda(p.getY())); bounds = null; } /** * Method to return a Rectangle that describes the orthogonal box in this Poly. * @return the Rectangle that describes this Poly. * If the Poly is not an orthogonal box, returns null. * IT IS NOT PERMITTED TO MODIFY THE RETURNED RECTANGLE * (because it is taken from the internal bounds of the Poly). */ public Rectangle2D getBox() { if (bitRectangle == 1) return getBounds2D(); else if (bitRectangle == 0) return null; bitRectangle = 0; if (points.length == 4) { // only closed polygons and text can be boxes if (style != Poly.Type.FILLED && style != Poly.Type.CLOSED && style != Poly.Type.TEXTBOX && style != Poly.Type.CROSSED) return null; } else if (points.length == 5) { if (style != Poly.Type.FILLED && style != Poly.Type.CLOSED && style != Poly.Type.OPENED && style != Poly.Type.OPENEDT1 && style != Poly.Type.OPENEDT2 && style != Poly.Type.OPENEDT3) return null; if (points[0].getX() != points[4].getX() || points[0].getY() != points[4].getY()) return null; } else return null; // make sure the polygon is rectangular and orthogonal if (points[0].getX() == points[1].getX() && points[2].getX() == points[3].getX() && points[0].getY() == points[3].getY() && points[1].getY() == points[2].getY()) { bitRectangle = 1; return getBounds2D(); } if (points[0].getX() == points[3].getX() && points[1].getX() == points[2].getX() && points[0].getY() == points[1].getY() && points[2].getY() == points[3].getY()) { bitRectangle = 1; return getBounds2D(); } return null; } /** * Method to compute the minimum size of this Polygon. * Only works with manhattan geometry. * @return the minimum dimension. */ public double getMinSize() { Rectangle2D box = getBox(); if (box == null) return 0; return Math.min(box.getWidth(), box.getHeight()); } /** * Method to compute the maximum size of this Polygon. * Only works with manhattan geometry. */ public double getMaxSize() { Rectangle2D box = getBox(); if (box == null) return 0; return Math.max(box.getWidth(), box.getHeight()); } /** * Method to compare this Poly to another. * @param polyOther the other Poly to compare. * @return true if the Polys are the same. */ public boolean polySame(PolyBase polyOther) { // polygons must have the same number of points Point2D [] points = getPoints(); Point2D [] pointsO = polyOther.getPoints(); if (points.length != pointsO.length) return false; // if both are boxes, compare their extents Rectangle2D box = getBox(); Rectangle2D boxO = polyOther.getBox(); if (box != null && boxO != null) { // compare box extents return box.equals(boxO); } if (box != null || boxO != null) return false; // compare these boxes the hard way for(int i=0; i<points.length; i++) if (!points[i].equals(pointsO[i])) return false; return true; } /** * Method to tell whether a coordinate is inside of this Poly. * The algorith relies on the Java class Area. Very slow. * @param pt the point in question. * @return true if the point is inside of this Poly. */ // public boolean isPointInsideArea(Point2D pt) // { // Area area = new Area(this); // return area.contains(pt.getX(), pt.getY()); // } /** * Method to determine if a point is inside a polygon. The method is based on counting * how many time an imaginary line cuts the polygon in one direction. * @param pt * @return */ private boolean isPointInsideCutAlgorithm(Point2D pt) { // general polygon containment by counting reference line intersections Point2D lastPoint = points[points.length-1]; //if (pt.equals(lastPoint)) return true; if (DBMath.areEquals(pt, lastPoint)) { return true; } Rectangle2D box = getBounds2D(); // The point is outside the bounding box of the polygon if (!DBMath.pointInRect(pt, box)) //pointInsideRect. It could be at the edges return false; int count = 0; for (Point2D thisPoint : points) { if (DBMath.areEquals(pt, thisPoint)) { return true; } // Checking if point is along polygon edge if (DBMath.isOnLine(thisPoint, lastPoint, pt)) { return true; } double ptY = pt.getY(); double lastY = lastPoint.getY(); double thisY = thisPoint.getY(); // not counting if the horizontal line passes through the point boolean skip = DBMath.areEquals(ptY, thisY) && DBMath.areEquals(ptY, lastY); if (!skip) { boolean thisPointGreaterY = DBMath.isGreaterThan(thisY, ptY); boolean lastPointGreaterY = DBMath.isGreaterThan(lastY, ptY); // doesn't intersect the horizontal line // pt[y] < s[Y] && pt[y] < e[Y] || pt[Y] > s[Y] && pt[Y] > e[Y] skip = (thisPointGreaterY && lastPointGreaterY) || (DBMath.isGreaterThan(ptY, thisY) && DBMath.isGreaterThan(ptY, lastY)); if (!skip) { // only counting half of the domain // at least one must be greater than pt[Y] and pt[X] // both X must be greater because it already checked if point is on the line. double ptX = pt.getX(); // not a vertical line. Horizontal lines won't make it to this point if (!DBMath.areEquals(thisY, lastY)) { ptX = thisPoint.getX() + (ptY - thisY) * (lastPoint.getX() - thisPoint.getX()) / (lastY - thisY); } // point must be at the right side of the point. Not checking if they are identical because // that was tested above. boolean pointGreaterX = DBMath.isGreaterThan(ptX, pt.getX()); if ((thisPointGreaterY || lastPointGreaterY) && pointGreaterX) count++; } } lastPoint = thisPoint; } boolean inside = (count != 0 && count%2 != 0); return (inside); } /** * Method to tell whether a coordinate is inside of this Poly. * This algorithm is based on angles. If the angle is 360 then the point is inside * @param pt the point in question. * @return true if the point is inside of this Poly. */ // private boolean isInsideGenericPolygonOriginal(Point2D pt) // { // // general polygon containment by summing angles to vertices // double ang = 0; // Point2D lastPoint = points[points.length-1]; // //if (pt.equals(lastPoint)) return true; // if (DBMath.areEquals(pt, lastPoint)) // { // return true; // } // Rectangle2D box = getBounds2D(); // // // The point is outside the bounding box of the polygon // if (!DBMath.pointInsideRect(pt, box)) // return false; // // int lastp = DBMath.figureAngle(pt, lastPoint); // for (Point2D thisPoint : points) // { // //if (pt.equals(thisPoint)) return true; // if (DBMath.areEquals(pt, thisPoint)) // { // return true; // } // // Checking if point is along polygon edge // if (DBMath.isOnLine(thisPoint, lastPoint, pt)) // { // return true; // } // int thisp = DBMath.figureAngle(pt, thisPoint); // int tang = lastp - thisp; // if (tang < -1800) // tang += 3600; // if (tang > 1800) // tang -= 3600; // ang += tang; // lastp = thisp; // lastPoint = thisPoint; // } // ang = Math.abs(ang); // //boolean completeCircle = ang == 0 || ang == 3600; // boolean oldCalculation = (!(ang <= points.length)); // return (oldCalculation); // //if (Math.abs(ang) <= points.length) return false; // //return true; // } public boolean isInside(Point2D pt) { if (style == Poly.Type.FILLED || style == Poly.Type.CLOSED || style == Poly.Type.CROSSED || style.isText()) { // If point is not in 2D bounding box -> is outside anyway Rectangle2D bounds2D = this.getBounds2D(); if (!DBMath.pointInRect(pt, bounds2D)) return false; // check rectangular case for containment Rectangle2D bounds = getBox(); if (bounds != null) { if (DBMath.pointInRect(pt, bounds)) return true; // special case: single point, take care of double precision error if (bounds.getWidth() == 0 && bounds.getHeight() == 0) { if (DBMath.areEquals(pt.getX(), bounds.getX()) && DBMath.areEquals(pt.getY(), bounds.getY())) return true; } return false; } // boolean method = isInsideGenericPolygonOriginal(pt); boolean method = isPointInsideCutAlgorithm(pt); // boolean method = isPointInsideArea(pt); // very slow. 3 times slower in 1 example return method; } if (style == Poly.Type.CROSS || style == Poly.Type.BIGCROSS) { if (DBMath.areEquals(getCenterX(), pt.getX()) && DBMath.areEquals(getCenterY(), pt.getY())) return true; return false; } if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3 || style == Poly.Type.VECTORS) { // first look for trivial inclusion by being a vertex //for(int i=0; i<points.length; i++) // if (pt.equals(points[i])) return true; for (Point2D point : points) if (DBMath.areEquals(pt, point)) return true; // see if the point is on one of the edges if (style == Poly.Type.VECTORS) { for(int i=0; i<points.length; i += 2) if (DBMath.isOnLine(points[i], points[i+1], pt)) return true; } else { for(int i=1; i<points.length; i++) if (DBMath.isOnLine(points[i-1], points[i], pt)) return true; } return false; } if (style == Poly.Type.CIRCLE || style == Poly.Type.THICKCIRCLE || style == Poly.Type.DISC) { double dist = points[0].distance(points[1]); double odist = points[0].distance(pt); if (odist < dist) return true; return false; } if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) { // first see if the point is at the proper angle from the center of the arc int ang = DBMath.figureAngle(points[0], pt); int endangle = DBMath.figureAngle(points[0], points[1]); int startangle = DBMath.figureAngle(points[0], points[2]); double angrange; if (endangle > startangle) { if (ang < startangle || ang > endangle) return false; angrange = endangle - startangle; } else { if (ang < startangle && ang > endangle) return false; angrange = 3600 - startangle + endangle; } // now see if the point is the proper distance from the center of the arc double dist = points[0].distance(pt); double wantdist; if (ang == startangle || angrange == 0) { wantdist = points[0].distance(points[1]); } else if (ang == endangle) { wantdist = points[0].distance(points[2]); } else { double startdist = points[0].distance(points[1]); double enddist = points[0].distance(points[2]); if (enddist == startdist) wantdist = startdist; else { wantdist = startdist + (ang - startangle) / angrange * (enddist - startdist); } } //if (dist == wantdist) return true; if (DBMath.areEquals(dist, wantdist)) return true; return false; } // I give up return false; } /** * Method to tell whether a coordinates of this Poly are inside of a Rectangle2D. * @param bounds the Rectangle2D in question. * @return true if this Poly is completely inside of the bounds. */ public boolean isInside(Rectangle2D bounds) { if (style == Poly.Type.CIRCLE || style == Poly.Type.THICKCIRCLE || style == Poly.Type.DISC) { Point2D ctr = points[0]; double dx = Math.abs(ctr.getX() - points[1].getX()); double dy = Math.abs(ctr.getY() - points[1].getY()); double rad = Math.max(dx, dy); if (!DBMath.pointInRect(new Point2D.Double(ctr.getX()+rad,ctr.getY()+rad), bounds)) return false; if (!DBMath.pointInRect(new Point2D.Double(ctr.getX()-rad,ctr.getY()-rad), bounds)) return false; //if (!bounds.contains(new Point2D.Double(ctr.getX()+rad,ctr.getY()+rad))) return false; //if (!bounds.contains(new Point2D.Double(ctr.getX()-rad,ctr.getY()-rad))) return false; return true; } for (Point2D p : points) { if (!DBMath.pointInRect(p, bounds)) return false; //if (!bounds.contains(points[i])) return false; } return true; } /** Method to check if point is part of the point set that defines * the polygon * @param point * @return true if found in points set */ public boolean isPointOnCorner(Point2D point) { for (Point2D p : points) { if (DBMath.areEquals(point,p)) return (true); } return (false); } /** * Method to reduce this Poly by the proper amount presuming that it describes a port connected to an arc. * This Poly is modified in place to reduce its size. * @param pi the PortInst that describes this Poly. * @param wid the width of the arc connected to this port-poly. * This should be the base width, not the actual width stored in memory. * @param angle the angle of the arc connected to this port-poly. * If negative, do not consider arc angle. */ public void reducePortPoly(PortInst pi, double wid, int angle) { // look down to the bottom level node/port PortOriginal fp = new PortOriginal(pi); // AffineTransform trans = fp.getTransformToTop(); NodeInst ni = fp.getBottomNodeInst(); // do not reduce port if not filled if (getStyle() != Poly.Type.FILLED && getStyle() != Poly.Type.CROSSED && getStyle() != Poly.Type.DISC) return; // do not reduce port areas on polygonally defined nodes if (ni.getTrace() != null) return; // determine amount to reduce port double realWid = wid / 2; // get bounding box of port polygon Rectangle2D portBounds = getBox(); if (portBounds == null) { // special case: nonrectangular port if (getStyle() == Poly.Type.DISC) { // shrink discs double dist = points[0].distance(points[1]); dist = Math.max(0, dist-realWid); points[1].setLocation(points[0].getX() + dist, points[0].getY()); return; } // cannot handle other forms of polygon yet return; } // determine the edge and center of the port polygon double bx = portBounds.getMinX(); double ux = portBounds.getMaxX(); double by = portBounds.getMinY(); double uy = portBounds.getMaxY(); // double cx = portBounds.getCenterX(); double cy = portBounds.getCenterY(); // compute the area of the nodeinst Rectangle2D r = ni.getBaseShape().getBounds2D(); double lx = r.getMinX(); double hx = r.getMaxX(); double ly = r.getMinY(); double hy = r.getMaxY(); // SizeOffset so = ni.getSizeOffset(); // Rectangle2D nodeBounds = ni.getBounds(); // Point2D lowerLeft = new Point2D.Double(nodeBounds.getMinX()+so.getLowXOffset(), nodeBounds.getMinY()+so.getLowYOffset()); // trans.transform(lowerLeft, lowerLeft); // Point2D upperRight = new Point2D.Double(nodeBounds.getMaxX()-so.getHighXOffset(), nodeBounds.getMaxY()-so.getHighYOffset()); // trans.transform(upperRight, upperRight); // double lx = lowerLeft.getX(); double hx = upperRight.getX(); // double ly = lowerLeft.getY(); double hy = upperRight.getY(); // if (lx > hx) { double swap = lx; lx = hx; hx = swap; } // if (ly > hy) { double swap = ly; ly = hy; hy = swap; } // do not reduce in X if arc is horizontal if (angle != 0 && angle != 1800) { // determine reduced port area lx = Math.max(bx, lx + realWid); hx = Math.min(ux, hx - realWid); if (hx < lx) hx = lx = (hx + lx) / 2; // only clip in X if the port area is within of the reduced node X area if (ux >= lx && bx <= hx) { for (Point2D point : points) { double x = point.getX(); if (x < lx)x = lx; if (x > hx) x = hx; point.setLocation(x, point.getY()); } } } // do not reduce in Y if arc is vertical if (angle != 900 && angle != 2700) { // determine reduced port area ly = Math.max(by, ly + realWid); hy = Math.min(uy, hy - realWid); if (hy < ly) hy = ly = (hy + ly) / 2; // only clip in Y if the port area is inside of the reduced node Y area if (uy >= ly && by <= hy) { for (Point2D point : points) { double y = point.getY(); if (y < ly) y = ly; if (y > hy) y = hy; point.setLocation(point.getX(), y); } } } } /** * Method to rotate a text Type according to the rotation of the object on which it resides. * @param origType the original text Type. * @param eObj the ElectricObject on which the text resides. * @return the new text Type that accounts for the rotation. */ public static Poly.Type rotateType(Poly.Type origType, ElectricObject eObj) { if (Poly.NEWTEXTTREATMENT) return origType; // centered text does not rotate its anchor if (origType == Poly.Type.TEXTCENT || origType == Poly.Type.TEXTBOX) return origType; // get node this sits on NodeInst ni; if (eObj instanceof NodeInst) { ni = (NodeInst)eObj; } else if (eObj instanceof Export) { Export pp = (Export)eObj; ni = pp.getOriginalPort().getNodeInst(); } else return origType; // no need to rotate anchor if the node is not transformed int nodeAngle = ni.getAngle(); if (nodeAngle == 0 && !ni.isMirroredAboutXAxis() && !ni.isMirroredAboutYAxis()) return origType; // can only rotate anchor when node is in a manhattan orientation if ((nodeAngle%900) != 0) return origType; // special case handling for left/right/top/bottom orientations if (origType == Poly.Type.TEXTLEFT || origType == Poly.Type.TEXTRIGHT || origType == Poly.Type.TEXTTOP || origType == Poly.Type.TEXTBOT) { boolean flipAnchor = false; if (ni.isMirroredAboutXAxis()) { if (ni.isMirroredAboutYAxis()) { // mirrored in both directions: flip only the 0-degree anchor if (nodeAngle == 0) flipAnchor = true; } else { // mirrored only U-D: flip conditionally if (origType == Poly.Type.TEXTLEFT || origType == Poly.Type.TEXTRIGHT) { if (nodeAngle == 900 || nodeAngle == 1800) flipAnchor = true; } else if (origType == Poly.Type.TEXTTOP || origType == Poly.Type.TEXTBOT) { if (nodeAngle == 0 || nodeAngle == 2700) flipAnchor = true; } } } else { if (ni.isMirroredAboutYAxis()) { // mirrored only L-R: flip conditionally if (origType == Poly.Type.TEXTLEFT || origType == Poly.Type.TEXTRIGHT) { if (nodeAngle == 0 || nodeAngle == 2700) flipAnchor = true; } else if (origType == Poly.Type.TEXTTOP || origType == Poly.Type.TEXTBOT) { if (nodeAngle == 900 || nodeAngle == 1800) flipAnchor = true; } } else { // not mirrored: flip only 180-degree anchor if (nodeAngle == 1800) flipAnchor = true; } } if (flipAnchor) { if (origType == Poly.Type.TEXTLEFT) return Poly.Type.TEXTRIGHT; if (origType == Poly.Type.TEXTRIGHT) return Poly.Type.TEXTLEFT; if (origType == Poly.Type.TEXTTOP) return Poly.Type.TEXTBOT; if (origType == Poly.Type.TEXTBOT) return Poly.Type.TEXTTOP; } return origType; } // determine angle of original style int origAngle = origType.getTextAngle(); if (ni.isMirroredAboutXAxis() != ni.isMirroredAboutYAxis() && ((origAngle%1800) == 0 || (origAngle%1800) == 1350)) origAngle += 1800; // determine change in angle because of node rotation Orientation orient = Orientation.fromJava(nodeAngle, ni.isMirroredAboutXAxis(), ni.isMirroredAboutYAxis()); AffineTransform trans = orient.pureRotate(); Point2D pt = new Point2D.Double(100, 0); trans.transform(pt, pt); int xAngle = GenMath.figureAngle(new Point2D.Double(0, 0), pt); if (ni.isMirroredAboutXAxis() != ni.isMirroredAboutYAxis() && ((origAngle%1800) == 450)) xAngle += 900; // determine new angle and style int angle = (origAngle + xAngle) % 3600; Poly.Type style = Poly.Type.getTextTypeFromAngle(angle); return style; } /** * Method to unrotate a text Type according to the rotation of the object on which it resides. * Unrotation implies converting apparent anchor information to actual stored anchor information * on a transformed node. For example, if the node is rotated, and the anchor appears to be at the * bottom, then the actual anchor that is stored with the node will be different (and when transformed * will appear to be at the bottom). * @param origType the original text Type. * @param eObj the ElectricObject on which the text resides. * @return the new text Type that accounts for the rotation. */ public static Poly.Type unRotateType(Poly.Type origType, ElectricObject eObj) { // centered text does not rotate its anchor if (origType == Poly.Type.TEXTCENT || origType == Poly.Type.TEXTBOX) return origType; // get node this sits on NodeInst ni; if (eObj instanceof NodeInst) { ni = (NodeInst)eObj; } else if (eObj instanceof Export) { Export pp = (Export)eObj; ni = pp.getOriginalPort().getNodeInst(); } else return origType; // no need to rotate anchor if the node is not transformed int nodeAngle = ni.getAngle(); if (nodeAngle == 0 && !ni.isMirroredAboutXAxis() && !ni.isMirroredAboutYAxis()) return origType; // can only rotate anchor when node is in a manhattan orientation if ((nodeAngle%900) != 0) return origType; // rotate the anchor int angle = origType.getTextAngle(); int rotAngle = ni.getAngle(); if (ni.isMirroredAboutXAxis() != ni.isMirroredAboutYAxis()) rotAngle = -rotAngle; Orientation orient = Orientation.fromJava(rotAngle, ni.isMirroredAboutXAxis(), ni.isMirroredAboutYAxis()); AffineTransform trans = orient.pureRotate(); // AffineTransform trans = NodeInst.pureRotate(rotAngle, ni.isMirroredAboutXAxis(), ni.isMirroredAboutYAxis()); Point2D pt = new Point2D.Double(100, 0); trans.transform(pt, pt); int xAngle = GenMath.figureAngle(new Point2D.Double(0, 0), pt); if (ni.isMirroredAboutXAxis() != ni.isMirroredAboutYAxis() && ((angle%1800) == 0 || (angle%1800) == 1350)) angle += 1800; angle = (angle - xAngle + 3600) % 3600; return Poly.Type.getTextTypeFromAngle(angle); } /** * Method to return the scaling factor between database and screen for the given text. * @param wnd the window with the text. * @param gv the GlyphVector describing the text. * @param style the anchor information for the text. * @param lX the low X bound of the polygon containing the text. * @param hX the high X bound of the polygon containing the text. * @param lY the low Y bound of the polygon containing the text. * @param hY the high Y bound of the polygon containing the text. * @return the scale of the text (from database to screen). */ protected double getTextScale(EditWindow0 wnd, GlyphVector gv, Poly.Type style, double lX, double hX, double lY, double hY) { double textScale = 1.0/wnd.getScale(); if (style == Poly.Type.TEXTBOX) { Rectangle2D glyphBounds = gv.getVisualBounds(); double textWidth = glyphBounds.getWidth() * textScale; if (textWidth > hX - lX) { // text too big for box: scale it down textScale *= (hX - lX) / textWidth; } } return textScale; } /** * Method to report the distance of a point to this Poly. * @param x coordinate of a point. * @param y coordinate of a point. * @return the distance of the point to the Poly. * The method returns a negative amount if the point is a direct hit on or inside * the polygon (the more negative, the closer to the center). */ public double polyDistance(double x, double y) { return polyDistance(new Rectangle2D.Double(x, y, 0, 0)); } /** * Method to report the distance of a rectangle or point to this Poly. * @param otherBounds the area to test for distance to the Poly. * @return the distance of the area to the Poly. * The method returns a negative amount if the point/area is a direct hit on or inside * the polygon (the more negative, the closer to the center). */ public double polyDistance(Rectangle2D otherBounds) { // get information about this Poly Rectangle2D polyBounds = getBounds2D(); double polyCX = polyBounds.getCenterX(); double polyCY = polyBounds.getCenterY(); Point2D polyCenter = new Point2D.Double(polyCX, polyCY); Poly.Type localStyle = style; boolean thisIsPoint = (polyBounds.getWidth() == 0 && polyBounds.getHeight() == 0); // get information about the other area being tested boolean otherIsPoint = (otherBounds.getWidth() == 0 && otherBounds.getHeight() == 0); double otherCX = otherBounds.getCenterX(); double otherCY = otherBounds.getCenterY(); Point2D otherPt = new Point2D.Double(otherCX, otherCY); // handle single point polygons if (thisIsPoint) { if (otherIsPoint) { if (polyCX == otherCX && polyCY == otherCY) return Double.MIN_VALUE; } else { if (otherBounds.contains(polyCenter)) return Double.MIN_VALUE; } return otherPt.distance(polyCenter); } // handle polygons that are filled in if (localStyle == Poly.Type.FILLED || localStyle == Poly.Type.CROSSED || localStyle.isText()) { if (otherIsPoint) { // give special returned value if point is a direct hit if (isInside(otherPt)) { return otherPt.distance(polyCenter) - Double.MAX_VALUE; } // if polygon is a box, use M.B.R. information Rectangle2D box = getBox(); if (box != null) { if (otherCX > box.getMaxX()) polyCX = otherCX - box.getMaxX(); else if (otherCX < box.getMinX()) polyCX = box.getMinX() - otherCX; else polyCX = 0; if (otherCY > box.getMaxY()) polyCY = otherCY - box.getMaxY(); else if (otherCY < box.getMinY()) polyCY = box.getMinY() - otherCY; else polyCY = 0; if (polyCX == 0 || polyCY == 0) return polyCX + polyCY; polyCenter.setLocation(polyCX, polyCY); return polyCenter.distance(new Point2D.Double(0,0)); } // point is outside of irregular polygon: fall into to next case localStyle = Poly.Type.CLOSED; } else { if (DBMath.rectsIntersect(otherBounds, polyBounds)) return Double.MIN_VALUE; return otherPt.distance(polyCenter); } } // handle closed outline figures if (localStyle == Poly.Type.CLOSED) { if (otherIsPoint) { double bestDist = Double.MAX_VALUE; Point2D lastPt = points[points.length-1]; for(int i=0; i<points.length; i++) { if (i != 0) lastPt = points[i-1]; Point2D thisPt = points[i]; // compute distance of close point to "otherPt" double dist = DBMath.distToLine(lastPt, thisPt, otherPt); if (dist < bestDist) bestDist = dist; } return bestDist; } else { if (DBMath.rectsIntersect(otherBounds, polyBounds)) return Double.MIN_VALUE; return otherPt.distance(polyCenter); } } // handle opened outline figures if (localStyle == Poly.Type.OPENED || localStyle == Poly.Type.OPENEDT1 || localStyle == Poly.Type.OPENEDT2 || localStyle == Poly.Type.OPENEDT3) { if (otherIsPoint) { double bestDist = Double.MAX_VALUE; for(int i=1; i<points.length; i++) { Point2D lastPt = points[i-1]; Point2D thisPt = points[i]; // compute distance of close point to "otherPt" double dist = DBMath.distToLine(lastPt, thisPt, otherPt); if (dist < bestDist) bestDist = dist; } return bestDist; } else { if (DBMath.rectsIntersect(otherBounds,polyBounds)) return Double.MIN_VALUE; return otherPt.distance(polyCenter); } } // handle outline vector lists if (localStyle == Poly.Type.VECTORS) { if (otherIsPoint) { double bestDist = Double.MAX_VALUE; for(int i=0; i<points.length; i += 2) { Point2D lastPt = points[i]; Point2D thisPt = points[i+1]; // compute distance of close point to "otherPt" double dist = DBMath.distToLine(lastPt, thisPt, otherPt); if (dist < bestDist) bestDist = dist; } return bestDist; } else { if (DBMath.rectsIntersect(otherBounds,polyBounds)) return Double.MIN_VALUE; return otherPt.distance(polyCenter); } } // handle circular objects if (localStyle == Poly.Type.CIRCLE || localStyle == Poly.Type.THICKCIRCLE || localStyle == Poly.Type.DISC) { double odist = points[0].distance(points[1]); double dist = points[0].distance(otherPt); if (otherIsPoint) { if (localStyle == Poly.Type.DISC && dist < odist) return dist-Double.MAX_VALUE; return Math.abs(dist-odist); } else { if (points[0].getX() + dist < otherBounds.getMinX()) return dist; if (points[0].getX() - dist > otherBounds.getMaxX()) return dist; if (points[0].getY() + dist < otherBounds.getMinY()) return dist; if (points[0].getY() - dist > otherBounds.getMaxY()) return dist; return Double.MIN_VALUE; } } if (localStyle == Poly.Type.CIRCLEARC || localStyle == Poly.Type.THICKCIRCLEARC) { if (otherIsPoint) { // determine closest point to ends of arc double sdist = otherPt.distance(points[1]); double edist = otherPt.distance(points[2]); double dist = Math.min(sdist, edist); // see if the point is in the segment of the arc int pang = DBMath.figureAngle(points[0], otherPt); int sang = DBMath.figureAngle(points[0], points[1]); int eang = DBMath.figureAngle(points[0], points[2]); if (eang > sang) { if (pang < eang && pang > sang) return dist; } else { if (pang < eang || pang > sang) return dist; } // point in arc: determine distance double odist = points[0].distance(points[1]); dist = points[0].distance(otherPt); return Math.abs(dist-odist); } else { Point2D [] savePoints = points; clipArc(otherBounds.getMinX(), otherBounds.getMaxX(), otherBounds.getMinY(), otherBounds.getMaxY()); Point2D [] newPoints = points; points = savePoints; if (newPoints.length > 0) return Double.MIN_VALUE; double dist = points[0].distance(points[1]); return points[0].distance(otherPt) - dist; } } // can't figure out others: use distance to polygon center return otherPt.distance(polyCenter); } /** * Method to calculate fast distance between two manhattan * polygons that do not intersect * @param polyOther the other polygon being examined with this. * @return non-negative distance if both polygons are manhattan types, * -1 if at least one of them is not manhattan. */ public double separationBox(PolyBase polyOther) { Rectangle2D thisBounds = getBox(); Rectangle2D otherBounds = polyOther.getBox(); // Both polygons must be manhattan-shaped if (thisBounds == null || otherBounds == null) return -1; double lX1 = thisBounds.getMinX(); double hX1 = thisBounds.getMaxX(); double lY1 = thisBounds.getMinY(); double hY1 = thisBounds.getMaxY(); double lX2 = otherBounds.getMinX(); double hX2 = otherBounds.getMaxX(); double lY2 = otherBounds.getMinY(); double hY2 = otherBounds.getMaxY(); double pdx = Math.max(lX2-hX1, lX1-hX2); double pdy = Math.max(lY2-hY1, lY1-hY2); double pd = (pdx > 0 && pdy > 0) ? // Diagonal Math.hypot(pdx, pdy) : Math.max(pdx, pdy); return pd; } /** * Method to return the distance between this Poly and another. * @param polyOther the other Poly to consider. * @return the distance between them (returns 0 if they touch or overlap). */ public double separation(PolyBase polyOther) { // stop now if they touch if (intersects(polyOther)) return 0; // look at all points on polygon 1 double minPD = 0; for(int i=0; i<points.length; i++) { Point2D c = polyOther.closestPoint(points[i]); double pd = c.distance(points[i]); if (pd <= 0) return 0; if (i == 0) minPD = pd; else { if (pd < minPD) minPD = pd; } } // look at all points on polygon 2 for (Point2D point : polyOther.points) { Point2D c = closestPoint(point); double pd = c.distance(point); if (pd <= 0) return 0; if (pd < minPD) minPD = pd; } // also compute manhattan separation and use it if better double minPDman = separationBox(polyOther); if (minPDman != -1 && minPDman < minPD) minPD = minPDman; return minPD; } /** * Method to find the point on this polygon closest to a given point. * @param pt the given point * @return a point on this Poly that is closest. */ public Point2D closestPoint(Point2D pt) { Poly.Type localStyle = style; if (localStyle == Poly.Type.FILLED || localStyle == Poly.Type.CROSSED || localStyle == Poly.Type.TEXTCENT || localStyle.isText()) { // filled polygon: check for regularity first Rectangle2D bounds = getBox(); if (bounds != null) { double x = pt.getX(); double y = pt.getY(); if (x < bounds.getMinX()) x = bounds.getMinX(); if (x > bounds.getMaxX()) x = bounds.getMaxX(); if (y < bounds.getMinY()) y = bounds.getMinY(); if (y > bounds.getMaxY()) y = bounds.getMaxY(); return new Point2D.Double(x, y); } if (localStyle == Poly.Type.FILLED) { if (isInside(pt)) return pt; } localStyle = Poly.Type.CLOSED; // FALLTHROUGH } if (localStyle == Poly.Type.CLOSED) { // check outline of description double bestDist = Double.MAX_VALUE; Point2D bestPoint = new Point2D.Double(); for(int i=0; i<points.length; i++) { int lastI; if (i == 0) lastI = points.length-1; else lastI = i-1; Point2D pc = DBMath.closestPointToSegment(points[lastI], points[i], pt); double dist = pc.distance(pt); if (dist > bestDist) continue; bestDist = dist; bestPoint.setLocation(pc); } return bestPoint; } if (localStyle == Poly.Type.OPENED || localStyle == Poly.Type.OPENEDT1 || localStyle == Poly.Type.OPENEDT2 || localStyle == Poly.Type.OPENEDT3) { // check outline of description double bestDist = Double.MAX_VALUE; Point2D bestPoint = new Point2D.Double(); for(int i=1; i<points.length; i++) { Point2D pc = DBMath.closestPointToSegment(points[i-1], points[i], pt); double dist = pc.distance(pt); if (dist > bestDist) continue; bestDist = dist; bestPoint.setLocation(pc); } return bestPoint; } if (localStyle == Poly.Type.VECTORS) { // check outline of description double bestDist = Double.MAX_VALUE; Point2D bestPoint = new Point2D.Double(); for(int i=0; i<points.length; i += 2) { Point2D pc = DBMath.closestPointToSegment(points[i], points[i+1], pt); double dist = pc.distance(pt); if (dist > bestDist) continue; bestDist = dist; bestPoint.setLocation(pc); } return bestPoint; } // presume single-point polygon and use the center Rectangle2D bounds = getBounds2D(); return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); } /** * Method to tell whether a point is inside of this Poly. * This method is a requirement of the Shape implementation. * @param x the X coordinate of the point. * @param y the Y coordinate of the point. * @return true if the point is inside the Poly. */ public boolean contains(double x, double y) { return isInside(new Point2D.Double(x, y)); } /** * Method to tell whether a point is inside of this Poly. * This method is a requirement of the Shape implementation. * @param p the point. * @return true if the point is inside the Poly. */ public boolean contains(Point2D p) { return isInside(p); } /** * Method to tell whether a rectangle is inside of this Poly. * This method is a requirement of the Shape implementation. * @param lX the X corner of the rectangle. * @param lY the Y corner of the rectangle. * @param w the width of the rectangle. * @param h the height of the rectangle. * @return true if the rectangle is inside the Poly. */ public boolean contains(double lX, double lY, double w, double h) { // first ensure all rectangle corners are inside of the polygon double hX = lX + w; double hY = lY + h; if (!isInside(new Point2D.Double(lX, lY)) || !isInside(new Point2D.Double(hX, lY)) || !isInside(new Point2D.Double(lX, hY)) || !isInside(new Point2D.Double(hX, hY))) return false; // because nonconvex polygons may pierce rectangle, check all edges for(int i=0; i<points.length; i++) { // get a polygon edge int last = i-1; if (last < 0) last = points.length-1; Point2D thisPt = new Point2D.Double(points[i].getX(), points[i].getY()); Point2D lastPt = new Point2D.Double(points[last].getX(), points[last].getY()); // if the edge is completely outside of the rectangle, it is OK boolean invisible = GenMath.clipLine(lastPt, thisPt, lX, hX, lY, hY); if (invisible) continue; // if the polygon edge line was not completely clipped, it must sit on the rectangle edge if (lastPt.getX() == thisPt.getX()) { if (lastPt.getY() == thisPt.getY()) { if (thisPt.getX() <= lX || thisPt.getX() >= hX || thisPt.getY() <= lY || thisPt.getY() >= hY) continue; } else { if (thisPt.getX() <= lX || thisPt.getX() >= hX) continue; } } if (lastPt.getY() == thisPt.getY()) { if (thisPt.getY() <= lY || thisPt.getY() >= hY) continue; } // polygon edge is inside of rectangle: no containment return false; } // all edges pass: rectangle is contained return true; } /** * Method to tell whether a rectangle is inside of this Poly. * This method is a requirement of the Shape implementation. * @param r the rectangle. * @return true if the rectangle is inside the Poly. */ public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Method to tell whether a rectangle intersects this Poly. * This method is a requirement of the Shape implementation. * THIS METHOD HAS NOT BEEN WRITTEN YET!!! * @param x the X corner of the rectangle. * @param y the Y corner of the rectangle. * @param w the width of the rectangle. * @param h the height of the rectangle. * @return true if the rectangle intersects the Poly. */ public boolean intersects(double x, double y, double w, double h) { throw new Error("intersects method not implemented in Poly.intersects()"); //return false; } /** * Method to tell whether a rectangle intersects this Poly. * This method is a requirement of the Shape implementation. * @param r the rectangle. * @return true if the rectangle intersects the Poly. */ public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** * Method to tell whether this Poly intersects another one. * @param polyOther the other Poly to test. * @return true if polygons intersect (that is, if any of their lines intersect). */ public boolean intersects(PolyBase polyOther) { // quit now if bounding boxes don't overlap Rectangle2D thisBounds = getBounds2D(); Rectangle2D otherBounds = polyOther.getBounds2D(); if (thisBounds.getMaxX() < otherBounds.getMinX() || otherBounds.getMaxX() < thisBounds.getMinX() || thisBounds.getMaxY() < otherBounds.getMinY() || otherBounds.getMaxY() < thisBounds.getMinY()) return false; // check each line in this Poly int count = points.length; for(int i=0; i<count; i++) { Point2D p; if (i == 0) { if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3 || style == Poly.Type.VECTORS) continue; p = points[count-1]; } else { p = points[i-1]; } Point2D t = points[i]; if (style == Poly.Type.VECTORS && (i&1) != 0) i++; if (p.getX() == t.getX() && p.getY() == t.getY()) continue; // compare this line with the other Poly if (Math.min(p.getX(),t.getX()) > otherBounds.getMaxX() || Math.max(p.getX(),t.getX()) < otherBounds.getMinX() || Math.min(p.getY(),t.getY()) > otherBounds.getMaxY() || Math.max(p.getY(),t.getY()) < otherBounds.getMinY()) continue; if (polyOther.lineIntersect(p, t)) return true; } return false; } /** * Method to find the intersection area of this poly with another poly. * Returns null if no intersection. * @param polyOther the other poly * @param overlappingEdges if non-null, this list will be filled with edges that overlap * between the two polygons. This is useful if the polygons touch, as the intersection area will be * empty. * @return the intersection area of the two, or null if none */ public List<PolyBase> getIntersection(PolyBase polyOther, List<Line2D> overlappingEdges) { // get overlapping edges if requested if (overlappingEdges != null) { List<Line2D> overlaps = getOverlappingEdges(polyOther); overlappingEdges.addAll(overlaps); } // get intersected area Area myArea = new Area(this); Area otherArea = new Area(polyOther); myArea.intersect(otherArea); List<PolyBase> polys = getPointsInArea(myArea, layer, true, false); if (polys == null) return null; return polys; } /** * Get the overlapping edges with other polygon. Returns an empty list if none. * @param polyOther the other polygon * @return a list of overlapping edges */ public List<Line2D> getOverlappingEdges(PolyBase polyOther) { List<Line2D> overlappingSegs = new ArrayList<Line2D>(); // quit now if bounding boxes don't overlap Rectangle2D thisBounds = getBounds2D(); Rectangle2D otherBounds = polyOther.getBounds2D(); if (thisBounds.getMaxX() < otherBounds.getMinX() || otherBounds.getMaxX() < thisBounds.getMinX() || thisBounds.getMaxY() < otherBounds.getMinY() || otherBounds.getMaxY() < thisBounds.getMinY()) return overlappingSegs; // check if any line segments intersect, and add the intersection // point to the list if points List<Line2D> myLineSegs = getLineSegments(); List<Line2D> otherLineSegs = polyOther.getLineSegments(); for (Line2D line1 : myLineSegs) { for (Line2D line2 : otherLineSegs) { Line2D seg = getLineOverlap(line1, line2); if (seg != null) overlappingSegs.add(seg); } } // remove single points if they are redundant with line segments List<Line2D> realLines = new ArrayList<Line2D>(); List<Line2D> points = new ArrayList<Line2D>(); for (Line2D seg : overlappingSegs) { if (seg.getP1().equals(seg.getP2())) points.add(seg); else realLines.add(seg); } for (Line2D p : points) { boolean redundant = false; for (Line2D seg : realLines) { if (p.getP1().equals(seg.getP1()) || p.getP1().equals(seg.getP2())) { redundant = true; break; } } if (!redundant) realLines.add(p); } return realLines; } // get the line segments of this polygon private List<Line2D> getLineSegments() { List<Line2D> lineSegs = new ArrayList<Line2D>(); for(int i=0; i<points.length; i++) { Point2D p; if (i == 0) { if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3 || style == Poly.Type.VECTORS) continue; p = points[points.length-1]; } else { p = points[i-1]; } Point2D t = points[i]; if (style == Poly.Type.VECTORS && (i&1) != 0) i++; if (p.getX() == t.getX() && p.getY() == t.getY()) continue; // see if this line intersects a line on the other poly lineSegs.add(new Line2D.Double(p, t)); } return lineSegs; } /** * Get the intersection point of two line segments, if any. * This also returns null if the two lines are the same line, * as all points on either lines are intersection points. * @param line1 the first line segment * @param line2 the second line segment * @return the intersection point, or null if none */ public static Point2D getLineSegmentIntersection(Line2D line1, Line2D line2) { if (!line1.intersectsLine(line2)) return null; double [] co1 = getLineCoeffs(line1); double [] co2 = getLineCoeffs(line2); // det = A1*B2 - A2*B1 double det = co1[0]*co2[1] - co2[0]*co1[1]; // if det == 0, lines are parallel, but already checked by intersection check above // if det == 0 and we got here, lines are the same line. if (det == 0) return null; // x = (B2*C1 - B1*C2)/det double x = (co2[1]*co1[2] - co1[1]*co2[2])/det; // y = (A1*C2 - A2*C1)/det double y = (co1[0]*co2[2] - co2[0]*co1[2])/det; if (x == -0.0) x = 0; if (y == -0.0) y = 0; return new Point2D.Double(x, y); } /** * Get the line segment that is common to both lines. Returns null if none. * @param line1 the first line segment * @param line2 the second line segment * @return the common line segment, or null if none */ public static Line2D getLineOverlap(Line2D line1, Line2D line2) { if (!line1.intersectsLine(line2)) return null; double [] co1 = getLineCoeffs(line1); double [] co2 = getLineCoeffs(line2); // det = A1*B2 - A2*B1 double det = co1[0]*co2[1] - co2[0]*co1[1]; // if det == 0, lines are parallel, but already checked by intersection check above // if det == 0 and we got here, lines are the same line. if (det != 0) return null; double minX1 = Math.min(line1.getX1(), line1.getX2()); double minX2 = Math.min(line2.getX1(), line2.getX2()); double minX = Math.max(minX1, minX2); double minY1 = Math.min(line1.getY1(), line1.getY2()); double minY2 = Math.min(line2.getY1(), line2.getY2()); double minY = Math.max(minY1, minY2); double maxX1 = Math.max(line1.getX1(), line1.getX2()); double maxX2 = Math.max(line2.getX1(), line2.getX2()); double maxX = Math.min(maxX1, maxX2); double maxY1 = Math.max(line1.getY1(), line1.getY2()); double maxY2 = Math.max(line2.getY1(), line2.getY2()); double maxY = Math.min(maxY1, maxY2); Point2D p1 = new Point2D.Double(minX, minY); Point2D p2 = new Point2D.Double(maxX, maxY); return new Line2D.Double(p1, p2); } /** * Get the coeffecients of the line of the form Ax + By = C. * Can't use y = Ax + B because it does not allow x = A type equations. * @param line the line * @return an array of the values A,B,C */ private static double [] getLineCoeffs(Line2D line) { double A = line.getP2().getY() - line.getP1().getY(); double B = line.getP1().getX() - line.getP2().getX(); double C = A * line.getP1().getX() + B * line.getP1().getY(); return new double [] {A,B,C}; } /** * Method to return true if the line segment from (px1,py1) to (tx1,ty1) * intersects any line in polygon "poly" */ private boolean lineIntersect(Point2D p1, Point2D t1) { int count = points.length; for(int i=0; i<count; i++) { Point2D p2; if (i == 0) { if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3 || style == Poly.Type.VECTORS) continue; p2 = points[count-1]; } else { p2 = points[i-1]; } Point2D t2 = points[i]; if (style == Poly.Type.VECTORS && (i&1) != 0) i++; // simple test: if it hit one of the points, it is an intersection if (t2.getX() == p1.getX() && t2.getY() == p1.getY()) return true; if (t2.getX() == t1.getX() && t2.getY() == t1.getY()) return true; // ignore zero-size segments if (p2.getX() == t2.getX() && p2.getY() == t2.getY()) continue; // special case: this line is vertical if (p2.getX() == t2.getX()) { // simple bounds check if (Math.min(p1.getX(),t1.getX()) > p2.getX() || Math.max(p1.getX(),t1.getX()) < p2.getX()) continue; if (p1.getX() == t1.getX()) { if (Math.min(p1.getY(),t1.getY()) > Math.max(p2.getY(),t2.getY()) || Math.max(p1.getY(),t1.getY()) < Math.min(p2.getY(),t2.getY())) continue; return true; } if (p1.getY() == t1.getY()) { if (Math.min(p2.getY(),t2.getY()) > p1.getY() || Math.max(p2.getY(),t2.getY()) < p1.getY()) continue; return true; } int ang = DBMath.figureAngle(p1, t1); Point2D inter = DBMath.intersect(p2, 900, p1, ang); if (inter == null) continue; if (inter.getX() != p2.getX() || inter.getY() < Math.min(p2.getY(),t2.getY()) || inter.getY() > Math.max(p2.getY(),t2.getY())) continue; return true; } // special case: this line is horizontal if (p2.getY() == t2.getY()) { // simple bounds check if (Math.min(p1.getY(),t1.getY()) > p2.getY() || Math.max(p1.getY(),t1.getY()) < p2.getY()) continue; if (p1.getY() == t1.getY()) { if (Math.min(p1.getX(),t1.getX()) > Math.max(p2.getX(),t2.getX()) || Math.max(p1.getX(),t1.getX()) < Math.min(p2.getX(),t2.getX())) continue; return true; } if (p1.getX() == t1.getX()) { if (Math.min(p2.getX(),t2.getX()) > p1.getX() || Math.max(p2.getX(),t2.getX()) < p1.getX()) continue; return true; } int ang = DBMath.figureAngle(p1, t1); Point2D inter = DBMath.intersect(p2, 0, p1, ang); if (inter == null) continue; if (inter.getY() != p2.getY() || inter.getX() < Math.min(p2.getX(),t2.getX()) || inter.getX() > Math.max(p2.getX(),t2.getX())) continue; return true; } // simple bounds check if (Math.min(p1.getX(),t1.getX()) > Math.max(p2.getX(),t2.getX()) || Math.max(p1.getX(),t1.getX()) < Math.min(p2.getX(),t2.getX()) || Math.min(p1.getY(),t1.getY()) > Math.max(p2.getY(),t2.getY()) || Math.max(p1.getY(),t1.getY()) < Math.min(p2.getY(),t2.getY())) continue; // general case of line intersection int ang1 = DBMath.figureAngle(p1, t1); int ang2 = DBMath.figureAngle(p2, t2); Point2D inter = DBMath.intersect(p2, ang2, p1, ang1); if (inter == null) continue; if (inter.getX() < Math.min(p2.getX(),t2.getX()) || inter.getX() > Math.max(p2.getX(),t2.getX()) || inter.getY() < Math.min(p2.getY(),t2.getY()) || inter.getY() > Math.max(p2.getY(),t2.getY()) || inter.getX() < Math.min(p1.getX(),t1.getX()) || inter.getX() > Math.max(p1.getX(),t1.getX()) || inter.getY() < Math.min(p1.getY(),t1.getY()) || inter.getY() > Math.max(p1.getY(),t1.getY())) continue; return true; } return false; } /** * Method to compute the perimeter of this Poly. * @return the perimeter of this Poly. */ public double getPerimeter() { double perim = 0; int start = 0; if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3) start = 1; for(int i=start; i<points.length; i++) { int j = i - 1; if (j < 0) j = points.length - 1; perim += points[i].distance(points[j]); } return perim; } /** * Method to compute longest edge. * @return the longest edge in this PolyBase. */ public double getMaxLength() { double max = 0; int start = 0; if (style == Poly.Type.OPENED || style == Poly.Type.OPENEDT1 || style == Poly.Type.OPENEDT2 || style == Poly.Type.OPENEDT3) start = 1; for(int i=start; i<points.length; i++) { int j = i - 1; if (j < 0) j = points.length - 1; double distance = points[i].distance(points[j]); if (max < distance) max = distance; } return max; } /** * Method to compute the area of this Poly. * @return the area of this Poly. Return always a positive number */ public double getArea() { if (style == Poly.Type.FILLED || style == Poly.Type.CLOSED || style == Poly.Type.CROSSED || style.isText()) { Rectangle2D bounds = getBox(); if (bounds != null) { double area = GenMath.getArea(bounds); /* now determine the sign of the area */ // double sign = 0; // if (points[0].getX() == points[1].getX()) // { // /* first line is vertical */ // sign = (points[2].getX() - points[1].getX()) * (points[1].getY() - points[0].getY()); // } else // { // /* first line is horizontal */ // sign = (points[1].getX() - points[0].getX()) * (points[1].getY() - points[2].getY()); // } //if (sign < 0) area = -area; return Math.abs(area); } return GenMath.getAreaOfPoints(points); } return 0; } /** * Method to return the X center coordinate of this Poly. * @return the X center coordinate of this Poly. */ public double getCenterX() { Rectangle2D b = getBounds2D(); return b.getCenterX(); } /** * Method to return the Y center coordinate of this Poly. * @return the Y center coordinate of this Poly. */ public double getCenterY() { Rectangle2D b = getBounds2D(); return b.getCenterY(); } /** * Method to return the center of the bounding box containing this PolyBase * @return EPoint representing the center of the PolyBase bounding box. */ public EPoint getCenter() { Rectangle2D b = getBounds2D(); return new EPoint(b.getCenterX(), b.getCenterY()); } /** * Method to return the bounds of this Poly. * @return the bounds of this Poly. */ public Rectangle2D getBounds2D() { if (bounds == null) calcBounds(); return bounds; } /** * Method to return the bounds of this Poly. * Nobody really uses this, but it is necessary for the implementation of Shape. * @return the bounds of this Poly. * @deprecated this is only implemented because Poly extends Shape. You should * be using getBounds2D() instead. */ public Rectangle getBounds() { if (bounds == null) calcBounds(); Rectangle2D r = getBounds2D(); return new Rectangle((int)r.getMinX(), (int)r.getMinY(), (int)r.getWidth(), (int)r.getHeight()); } /** * Method to change the value of a point in the PolyBase. * @param pt the index of the point to change. * @param x the new X value. * @param y the new Y value. */ public void setPoint(int pt, double x, double y) { points[pt].setLocation(x, y); bounds = null; } private void calcBounds() { bounds = null; if (style == Poly.Type.CIRCLE || style == Poly.Type.THICKCIRCLE || style == Poly.Type.DISC) { double cX = points[0].getX(); double cY = points[0].getY(); double radius = points[0].distance(points[1]); double diameter = radius * 2; bounds = new Rectangle2D.Double(cX - radius, cY - radius, diameter, diameter); return; } if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) { bounds = GenMath.arcBBox(points[1], points[2], points[0]); return; } if (points.length > 0) { double lX = points[0].getX(); double hX = lX; double lY = points[0].getY(); double hY = lY; for (int i = 1; i < points.length; i++) { double x = points[i].getX(); double y = points[i].getY(); if (x < lX) lX = x; if (x > hX) hX = x; if (y < lY) lY = y; if (y > hY) hY = y; } bounds = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY); // Back on Oct 1 //bounds = new Rectangle2D.Double(DBMath.round(lX), DBMath.round(lY), DBMath.round(hX-lX), DBMath.round(hY-lY)); } if (bounds == null) bounds = new Rectangle2D.Double(); } /** * Attempt to control rounding errors in input libraries */ public void roundPoints() { bounds = null; for (Point2D point : points) { point.setLocation(DBMath.round(point.getX()), DBMath.round(point.getY())); } } /** * Method to retrieve all loops that are part of this PolyBase, * sorted by area. * @return the List of loops. */ // public List<PolyBase> getSortedLoops() // { // Collection<PolyBase> set = getPointsInArea(new Area(this), layer, true, false, null); // List<PolyBase> list = new ArrayList<PolyBase>(set); // Collections.sort(list, AREA_COMPARATOR); // return (list); // } /** * Class to compare PolyBase objects */ private static Comparator<PolyBase> AREA_COMPARATOR = new Comparator<PolyBase>() { /** * Compares PolyBase objects based on area. * This method doesn't guarantee (compare(x, y)==0) == (x.equals(y)) * @param p1 first object to be compared. * @param p2 second object to be compared. * @return Returns a negative integer, zero, or a positive integer as the * first object has smaller than, equal to, or greater area than the second. * @throws ClassCastException if the arguments' types are not PolyBase. */ public int compare(PolyBase p1, PolyBase p2) { double diff = p1.getArea() - p2.getArea(); if (diff < 0.0) return -1; if (diff > 0.0) return 1; return 0; } }; /** * Static method to get PolyBase elements associated with an Area. * @param area Java2D structure containing the geometrical information * @param layer the Layer to examine. * @param simple if true, polygons with inner loops will return in sample Poly. * @param includeLastPoint true to include the last point. * @return List of PolyBase elements. */ public static List<PolyBase> getPointsInArea(Area area, Layer layer, boolean simple, boolean includeLastPoint) { if (area == null) return null; boolean isSingular = area.isSingular(); // Complex algorithm to detect loops if (!isSingular) return (getPointsFromComplex(area, layer)); double [] coords = new double[6]; List<Point2D> pointList = new ArrayList<Point2D>(); Point2D lastMoveTo = null; List<PolyBase> toDelete = new ArrayList<PolyBase>(); List<PolyBase> polyList = new ArrayList<PolyBase>(); // Gilda: best practice note: System.arraycopy for(PathIterator pIt = area.getPathIterator(null); !pIt.isDone(); ) { int type = pIt.currentSegment(coords); if (type == PathIterator.SEG_CLOSE) { if (includeLastPoint && lastMoveTo != null) pointList.add(lastMoveTo); Point2D [] points = new Point2D[pointList.size()]; int i = 0; for(Point2D p : pointList) points[i++] = p; PolyBase poly = new PolyBase(points); poly.setLayer(layer); poly.setStyle(Poly.Type.FILLED); lastMoveTo = null; toDelete.clear(); if (!simple && !isSingular) { for (PolyBase pn : polyList) { if (pn.contains(pointList.get(0)) || poly.contains(pn.getPoints()[0])) { points = pn.getPoints(); for (i = 0; i < points.length; i++) pointList.add(points[i]); Point2D[] newPoints = new Point2D[pointList.size()]; System.arraycopy(pointList.toArray(), 0, newPoints, 0, pointList.size()); poly = new PolyBase(newPoints); toDelete.add(pn); } } } if (poly != null) polyList.add(poly); polyList.removeAll(toDelete); pointList.clear(); } else if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO) { Point2D pt = new Point2D.Double(coords[0], coords[1]); pointList.add(pt); if (type == PathIterator.SEG_MOVETO) lastMoveTo = pt; } pIt.next(); } return polyList; } // Creating a tree for finding the loops public static class PolyBaseTree { List<PolyBaseTree> sons; PolyBase poly; PolyBaseTree(PolyBase p) { poly = p; sons = new ArrayList<PolyBaseTree>(); } public List<PolyBaseTree> getSons() {return sons;} public PolyBase getPoly() {return poly;} void getLoops(int level, Stack<PolyBase> stack) { // Starting of a new polygon if (level%2== 0) { stack.push(poly); } else { PolyBase top = stack.pop(); Point2D [] points = new Point2D[top.getPoints().length+poly.getPoints().length + 2]; System.arraycopy(top.getPoints(), 0, points, 0, top.getPoints().length); // Adding the first point at the end to close the first loop points[top.getPoints().length] = (Point2D)top.getPoints()[0].clone(); System.arraycopy(poly.getPoints(), 0, points, top.getPoints().length+1, poly.getPoints().length); points[points.length-1] = (Point2D)poly.getPoints()[0].clone(); PolyBase p = new PolyBase(points); p.setLayer(poly.getLayerOrPseudoLayer()); // they are supposed to belong to the same layer stack.push(p); } level++; for (PolyBaseTree t : sons) t.getLoops(level, stack); } boolean add(PolyBaseTree t) { if (!poly.contains(t.poly.getPoints()[0])) { // Belong to another root return false; } if (sons.size() == 0) { double a = poly.getArea(); double b = t.poly.getArea(); sons.add(t); if (a < b) { assert(false); System.out.println("Should this happen"); PolyBase c = t.poly; t.poly = poly; poly = c; } } else { for (PolyBaseTree b : sons) { PolyBase pn = b.poly; if (pn.contains(t.poly.getPoints()[0])) { return (b.add(t)); } // test very expensive. else if (Job.getDebug() && t.poly.contains(pn.getPoints()[0])) { assert(false); System.out.println("Bad happen"); } } sons.add(t); } return true; } } // This assumes the algorithm starts with external loop public static List<PolyBaseTree> getPolyTrees(Area area, Layer layer) { List<PolyBase> list = getLoopsFromArea(area, layer); List<PolyBaseTree> roots = getTreesFromLoops(list); return roots; } // Get trees from loops public static List<PolyBaseTree> getTreesFromLoops(List<PolyBase> list) { List<PolyBaseTree> roots = new ArrayList<PolyBaseTree>(); // areas are sorted from min to max // Build the hierarchy with loops for (int i = list.size()-1; i > -1; i--) { PolyBaseTree t = new PolyBaseTree(list.get(i)); // Check all possible roots boolean added = false; for (PolyBaseTree r : roots) { if (r.add(t)) { added = true; break; } } if (!added) roots.add(t); } return roots; } // Get Loops public static List<PolyBase> getLoopsFromArea(Area area, Layer layer) { if (area == null) return null; double [] coords = new double[6]; List<Point2D> pointList = new ArrayList<Point2D>(); List<PolyBase> list = new ArrayList<PolyBase>(); for(PathIterator pIt = area.getPathIterator(null); !pIt.isDone(); ) { int type = pIt.currentSegment(coords); if (type == PathIterator.SEG_CLOSE) { // ignore zero-size polygons boolean hasArea; if (ALLOWTINYPOLYGONS) hasArea = true; else { hasArea = false; for(int i=1; i<pointList.size(); i++) { if (pointList.get(i-1).distance(pointList.get(i)) > .00001) { hasArea = true; break; } } } if (hasArea) { Point2D [] points = new Point2D[pointList.size()]; System.arraycopy(pointList.toArray(), 0, points, 0, pointList.size()); PolyBase poly = new PolyBase(points); poly.setLayer(layer); poly.setStyle(Poly.Type.FILLED); list.add(poly); } pointList.clear(); } else if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO) { Point2D pt = new Point2D.Double(coords[0], coords[1]); pointList.add(pt); } pIt.next(); } Collections.sort(list, AREA_COMPARATOR); return list; } // This assumes the algorithm starts with external loop private static List<PolyBase> getPointsFromComplex(Area area, Layer layer) { List<PolyBase> list = getLoopsFromArea(area, layer); List<PolyBaseTree> roots = getTreesFromLoops(list); list.clear(); // get loops from all tree roots. Even loops start a new poly for (PolyBaseTree r : roots) { int count = 0; Stack<PolyBase> s = new Stack<PolyBase>(); r.getLoops(count, s); list.addAll(s); } return list; } private class PolyPathIterator implements PathIterator { int idx = 0; AffineTransform trans; public PolyPathIterator(AffineTransform at) { this.trans = at; } public int getWindingRule() { return WIND_EVEN_ODD; } public boolean isDone() { return idx > points.length; } public void next() { idx++; } public int currentSegment(float[] coords) { if (idx >= points.length) { return SEG_CLOSE; } coords[0] = (float) points[idx].getX(); coords[1] = (float) points[idx].getY(); if (trans != null) { trans.transform(coords, 0, coords, 0, 1); } return (idx == 0 ? SEG_MOVETO : SEG_LINETO); } public int currentSegment(double[] coords) { if (idx >= points.length) { return SEG_CLOSE; } coords[0] = points[idx].getX(); coords[1] = points[idx].getY(); if (trans != null) { trans.transform(coords, 0, coords, 0, 1); } return (idx == 0 ? SEG_MOVETO : SEG_LINETO); } } /** * Method to return a PathIterator for this Poly after a transformation. * This method is a requirement of the Shape implementation. * @param at the transformation to apply. * @return the PathIterator. */ public PathIterator getPathIterator(AffineTransform at) { return new PolyPathIterator(at); } /** * Method to return a PathIterator with a particular flatness for this Poly after a transformation. * This method is a requirement of the Shape implementation. * @param at the transformation to apply. * @param flatness the required flatness. * @return the PathIterator. */ public PathIterator getPathIterator(AffineTransform at, double flatness) { return getPathIterator(at); } /** * Initiative CrossLibCopy. * It should be equals */ public boolean compare(Object obj, StringBuffer buffer) { if (this == obj) return (true); if (obj == null || getClass() != obj.getClass()) return (false); Poly poly = (Poly)obj; if (getLayerOrPseudoLayer() != poly.getLayerOrPseudoLayer()) { // Don't put until polys are sorted by layer /* if (buffer != null) buffer.append("Elements belong to different layers " + getLayer().getName() + " found in " + poly.getLayer().getName() + "\n"); */ return (false); } // It should be covered by previous comparison //if (layer.getFunction() != poly.getLayer().getFunction()) return (false); boolean geometryCheck = polySame(poly); /* if (!geometryCheck && buffer != null) buffer.append("Elements don't represent same geometry " + getName() + " found in " + poly.getName() + "\n"); */ return (geometryCheck); } /** * Method to crop the box in the reference parameters (lx-hx, ly-hy) * against the box in (bx-ux, by-uy). If the box is cropped into oblivion, * returns 1. If the boxes overlap but cannot be cleanly cropped, * returns -1. Otherwise the box is cropped and zero is returned */ public static int cropBox(Rectangle2D bounds, Rectangle2D PUBox) { // if the two boxes don't touch, just return double bX = PUBox.getMinX(); double uX = PUBox.getMaxX(); double bY = PUBox.getMinY(); double uY = PUBox.getMaxY(); double lX = bounds.getMinX(); double hX = bounds.getMaxX(); double lY = bounds.getMinY(); double hY = bounds.getMaxY(); // !DBMath.isGreaterThan(hX, bX) == bX >= hX if (!DBMath.isGreaterThan(hX, bX) || !DBMath.isGreaterThan(hY, bY) || !DBMath.isGreaterThan(uX, lX) || !DBMath.isGreaterThan(uY, lY)) return 0; //if (bX >= hX || bY >= hY || uX <= lX || uY <= lY) return 0; // if the box to be cropped is within the other, say so boolean blX = !DBMath.isGreaterThan(bX, lX); boolean uhX = !DBMath.isGreaterThan(hX, uX); boolean blY = !DBMath.isGreaterThan(bY, lY); boolean uhY = !DBMath.isGreaterThan(hY, uY); //if (bX <= lX && uX >= hX && bY <= lY && uY >= hY) return 1; if (blX && uhX && blY && uhY) return 1; // see which direction is being cropped double xoverlap = Math.min(hX, uX) - Math.max(lX, bX); double yoverlap = Math.min(hY, uY) - Math.max(lY, bY); if (xoverlap > yoverlap) { // one above the other: crop in Y if (blX && uhX) //if (bX <= lX && uX >= hX) { // it covers in X...do the crop //if (uY >= hY) hY = bY; if (!DBMath.isGreaterThan(hY, uY)) hY = bY; //if (bY <= lY) lY = uY; if (blY) lY = uY; //if (hY <= lY) return 1; if (!DBMath.isGreaterThan(hY, lY)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } } else { // one next to the other: crop in X if (blY && uhY) //if (bY <= lY && uY >= hY) { // it covers in Y...crop in X //if (uX >= hX) hX = bX; if (!DBMath.isGreaterThan(hX, uX)) hX = bX; //if (bX <= lX) lX = uX; if (blX) lX = uX; //if (hX <= lX) return 1; if (!DBMath.isGreaterThan(hX, lX)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } } return -1; } /** * Method to crop the box in the reference parameters (lx-hx, ly-hy) * against the box in (bx-ux, by-uy). If the box is cropped into oblivion, * returns 1. If the boxes overlap but cannot be cleanly cropped, * returns -1. If boxes don't overlap, returns -2. * Otherwise the box is cropped and zero is returned */ public static int cropBoxComplete(Rectangle2D bounds, Rectangle2D PUBox) { // if the two boxes don't touch, just return double bX = PUBox.getMinX(); double uX = PUBox.getMaxX(); double bY = PUBox.getMinY(); double uY = PUBox.getMaxY(); double lX = bounds.getMinX(); double hX = bounds.getMaxX(); double lY = bounds.getMinY(); double hY = bounds.getMaxY(); if (!DBMath.isGreaterThan(hX, bX) || !DBMath.isGreaterThan(hY, bY) || !DBMath.isGreaterThan(uX, lX) || !DBMath.isGreaterThan(uY, lY)) return -2; // if the box to be cropped is within the other, say so boolean blX = !DBMath.isGreaterThan(bX, lX); boolean uhX = !DBMath.isGreaterThan(hX, uX); boolean blY = !DBMath.isGreaterThan(bY, lY); boolean uhY = !DBMath.isGreaterThan(hY, uY); if (blX && uhX && blY && uhY) return 1; // Crop in both directions if possible, self-contained case // covered already if (bX <= lX) lX = uX; if (bY >= lY) hY = bY; if (uY <= hY) lY = hY; if (hX <= uX) hX = bX; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } /** * Method to crop the box in the reference parameters (lx-hx, ly-hy) * against the box in (bx-ux, by-uy). If the box is cropped into oblivion, * returns 1. If the boxes overlap but cannot be cleanly cropped, * returns -1. Otherwise the box is cropped and zero is returned */ public static int halfCropBox(Rectangle2D bounds, Rectangle2D limit) { double bX = limit.getMinX(); double uX = limit.getMaxX(); double bY = limit.getMinY(); double uY = limit.getMaxY(); double lX = bounds.getMinX(); double hX = bounds.getMaxX(); double lY = bounds.getMinY(); double hY = bounds.getMaxY(); // if the two boxes don't touch, just return if (!DBMath.isGreaterThan(hX, bX) || !DBMath.isGreaterThan(hY, bY) || !DBMath.isGreaterThan(uX, lX) || !DBMath.isGreaterThan(uY, lY)) return 0; // if the box to be cropped is within the other, figure out which half to remove boolean blX = !DBMath.isGreaterThan(bX, lX); boolean uhX = !DBMath.isGreaterThan(hX, uX); boolean blY = !DBMath.isGreaterThan(bY, lY); boolean uhY = !DBMath.isGreaterThan(hY, uY); if (blX && uhX && blY && uhY) { double lxe = lX - bX; double hxe = uX - hX; double lye = lY - bY; double hye = uY - hY; double biggestExt = Math.max(Math.max(lxe, hxe), Math.max(lye, hye)); if (DBMath.areEquals(biggestExt, 0)) return 1; if (DBMath.areEquals(lxe, biggestExt)) { lX = (lX + uX) / 2; if (!DBMath.isGreaterThan(hX, lX)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } if (DBMath.areEquals(hxe, biggestExt)) { hX = (hX + bX) / 2; if (!DBMath.isGreaterThan(hX, lX)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } if (DBMath.areEquals(lye, biggestExt)) { lY = (lY + uY) / 2; if (!DBMath.isGreaterThan(hY, lY)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } if (DBMath.areEquals(hye, biggestExt)) { hY = (hY + bY) / 2; if (!DBMath.isGreaterThan(hY, lY)) return 1; bounds.setRect(lX, lY, hX-lX, hY-lY); return 0; } } // reduce (lx-hx,lY-hy) bY (bX-uX,bY-uY) boolean crops = false; if (blX && uhX) { // it covers in X...crop in Y if (!DBMath.isGreaterThan(hY, uY)) hY = (hY + bY) / 2; if (blY) lY = (lY + uY) / 2; bounds.setRect(lX, lY, hX-lX, hY-lY); crops = true; } if (blY && uhY) { // it covers in Y...crop in X if (!DBMath.isGreaterThan(hX, uX)) hX = (hX + bX) / 2; if (blX) lX = (lX + uX) / 2; bounds.setRect(lX, lY, hX-lX, hY-lY); crops = true; } if (!crops) return -1; return 0; } private static class AngleList { private double angle; private double x, y; AngleList(Point2D pt) { x = pt.getX(); y = pt.getY(); } } /** * Method to clip a curved polygon (CIRCLE, THICKCIRCLE, DISC, CIRCLEARC, or THICKCIRCLEARC) * against the rectangle lx <= X <= hx and ly <= Y <= hy. * Adjusts the polygon to contain the visible portions. */ public void clipArc(double lx, double hx, double ly, double hy) { double plx = bounds.getMinX(); double phx = bounds.getMaxX(); double ply = bounds.getMinY(); double phy = bounds.getMaxY(); // if not clipped, stop now if (plx >= lx && phx <= hx && ply >= ly && phy <= hy) return; // if totally invisible, blank the polygon if (plx > hx || phx < lx || ply > hy || phy < ly) { points = new Point2D[0]; return; } // initialize list of relevant points double xc = points[0].getX(); double yc = points[0].getY(); double xp = points[1].getX(); double yp = points[1].getY(); List<AngleList> curveList = new ArrayList<AngleList>(); if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) { // include arc endpoints AngleList al1 = new AngleList(points[1]); double dx = xp - xc; double dy = yp - yc; if (dx == 0.0 && dy == 0.0) { System.out.println("Domain error doing circle/circle tangents"); points = new Point2D[0]; return; } al1.angle = Math.atan2(dy, dx); curveList.add(al1); AngleList al2 = new AngleList(points[2]); dx = al1.x-xc; dy = al1.y-yc; if (dx == 0.0 && dy == 0.0) { System.out.println("Domain error doing circle/circle tangents"); points = new Point2D[0]; return; } al2.angle = Math.atan2(dy, dx); curveList.add(al2); } int initialCount = curveList.size(); // find intersection points along left edge Point2D i1 = new Point2D.Double(lx, ly); Point2D i2 = new Point2D.Double(lx, hy); int ints = circlelineintersection(points[0], points[1], i1, i2, 0); if (ints > 0) curveList.add(new AngleList(i1)); if (ints > 1) curveList.add(new AngleList(i2)); // find intersection points along top edge i1 = new Point2D.Double(lx, hy); i2 = new Point2D.Double(hx, hy); ints = circlelineintersection(points[0], points[1], i1, i2, 0); if (ints > 0) curveList.add(new AngleList(i1)); if (ints > 1) curveList.add(new AngleList(i2)); // find intersection points along right edge i1 = new Point2D.Double(hx, hy); i2 = new Point2D.Double(hx, ly); ints = circlelineintersection(points[0], points[1], i1, i2, 0); if (ints > 0) curveList.add(new AngleList(i1)); if (ints > 1) curveList.add(new AngleList(i2)); // find intersection points along bottom edge i1 = new Point2D.Double(hx, ly); i2 = new Point2D.Double(lx, ly); ints = circlelineintersection(points[0], points[1], i1, i2, 0); if (ints > 0) curveList.add(new AngleList(i1)); if (ints > 1) curveList.add(new AngleList(i2)); // if there are no intersections, arc is invisible if (curveList.size() == initialCount) { points = new Point2D[0]; return; } // determine angle of intersection points for(int i=initialCount; i<curveList.size(); i++) { AngleList al = curveList.get(i); if (al.y == yc && al.x == xc) { System.out.println("Warning: instability ahead"); points = new Point2D[0]; return; } double dx = al.x - xc; double dy = al.y - yc; if (dx == 0.0 && dy == 0.0) { System.out.println("Domain error doing circle/circle tangents"); points = new Point2D[0]; return; } al.angle = Math.atan2(dy, dx); } // reject points not on the arc if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) { int j = 2; AngleList al0 = curveList.get(0); AngleList al1 = curveList.get(1); for(int i=2; i<curveList.size(); i++) { AngleList al = curveList.get(i); if (al0.angle > al1.angle) { if (al.angle > al0.angle || al.angle < al1.angle) continue; } else { if (al.angle > al0.angle && al.angle < al1.angle) continue; } AngleList alj = curveList.get(j); alj.x = al.x; alj.y = al.y; alj.angle = al.angle; j++; } while (curveList.size() > j) curveList.remove(curveList.size()-1); // make sure the start of the arc is the first point al0 = curveList.get(0); for (AngleList al : curveList) { if (al.angle > al0.angle) al.angle -= Math.PI*2.0; } } else { // make sure all angles are negative for (AngleList al : curveList) { if (al.angle > 0.0) al.angle -= Math.PI*2.0; } } // sort by angle Collections.sort(curveList, new AngleListDescending()); // for full circles, add in starting point to complete circle if (style != Poly.Type.CIRCLEARC && style != Poly.Type.THICKCIRCLEARC) { AngleList al0 = curveList.get(0); AngleList alNew = new AngleList(new Point2D.Double(al0.x, al0.y)); alNew.angle = al0.angle - Math.PI*2.0; curveList.add(alNew); } // now examine each segment and add it, if it is in the window double radius = points[0].distance(points[1]); List<Point2D.Double> newIn = new ArrayList<Point2D.Double>(); for(int i=1; i<curveList.size(); i++) { int prev = i-1; AngleList al = curveList.get(i); AngleList alP = curveList.get(prev); double midAngle = (alP.angle + al.angle) / 2.0; while (midAngle < -Math.PI) midAngle += Math.PI * 2.0; double midx = xc + radius * Math.cos(midAngle); double midy = yc + radius * Math.sin(midAngle); if (midx < lx || midx > hx || midy < ly || midy > hy) continue; // add this segment newIn.add(new Point2D.Double(xc, yc)); newIn.add(new Point2D.Double(alP.x, alP.y)); newIn.add(new Point2D.Double(al.x, al.y)); } points = new Point2D[newIn.size()]; for(int i=0; i<newIn.size(); i++) points[i] = newIn.get(i); if (style == Poly.Type.THICKCIRCLE) style = Poly.Type.THICKCIRCLEARC; else if (style == Poly.Type.CIRCLE) style = Poly.Type.CIRCLEARC; } /** * Helper class for doing curve clipping. */ private static class AngleListDescending implements Comparator<AngleList> { public int compare(AngleList c1, AngleList c2) { double diff = c2.angle - c1.angle; if (diff < 0.0) return -1; if (diff > 0.0) return 1; return 0; } } /** * Method to find the intersection points between the circle at (icx,icy) with point (isx,isy) * and the line from (lx1,ly1) to (lx2,ly2). Returns the two points in (ix1,iy1) and (ix2,iy2). * Allows intersection tolerance of "tolerance". * Returns the number of intersection points (0 if none, 1 if tangent, 2 if intersecting). */ private int circlelineintersection(Point2D ctr, Point2D edge, Point2D from, Point2D to, double tolerance) { double icx = ctr.getX(); double icy = ctr.getY(); double isx = edge.getX(); double isy = edge.getY(); double lx1 = from.getX(); double ly1 = from.getY(); double lx2 = to.getX(); double ly2 = to.getY(); // construct a line that is perpendicular to the intersection line and passes // through the circle center. It meets the intersection line at (segx, segy) double segx = 0, segy = 0; if (ly1 == ly2) { segx = icx; segy = ly1; } else if (lx1 == lx2) { segx = lx1; segy = icy; } else { // compute equation of the line double fx = lx1 - lx2; double fy = ly1 - ly2; double m = fy / fx; double b = -lx1; b *= m; b += ly1; // compute perpendicular to line through the point double mi = -1.0/m; double bi = -icx; bi *= mi; bi += icy; // compute intersection of the lines segx = (bi-b) / (m-mi); segy = m * segx + b; } // special case when line passes through the circle center if (segx == icx && segy == icy) { double fx = isx - icx; double fy = isy - icy; double radius = Math.hypot(fx, fy); fx = lx2 - lx1; fy = ly2 - ly1; if (fx == 0.0 && fy == 0.0) { System.out.println("Domain error doing circle/line intersection"); return 0; } double angle = Math.atan2(fy, fx); from.setLocation(icx + Math.cos(angle) * radius, icy + Math.sin(angle) * radius); to.setLocation(icx + Math.cos(-angle) * radius, icy + Math.sin(-angle) * radius); } else { // construct a right triangle with the three points: (icx, icy), (segx, segy), and (ix1,iy1) // the right angle is at the point (segx, segy) and the hypotenuse is the circle radius // The unknown point is (ix1, iy1), the intersection of the line and the circle. // To find it, determine the angle at the point (icx, icy) double fx = isx - icx; double fy = isy - icy; double radius = Math.hypot(fx, fy); fx = segx - icx; fy = segy - icy; double adjacent = Math.hypot(fx, fy); // if they are within tolerance, accept if (Math.abs(adjacent - radius) < tolerance) { from.setLocation(segx, segy); return 1; } // if the point is outside of the circle, quit if (adjacent > radius) return 0; // for zero radius, use circle center if (radius == 0.0) { from.setLocation(icx, icy); to.setLocation(icx, icy); } else { // now determine the angle from the center to the point (segx, segy) and offset that angle // by "angle". Then project it by "radius" to get the two intersection points double angle = Math.acos(adjacent / radius); fx = segx - icx; fy = segy - icy; if (fx == 0.0 && fy == 0.0) { System.out.println("Domain error doing line/circle intersection"); return 0; } double intangle = Math.atan2(fy, fx); double a1 = intangle - angle; double a2 = intangle + angle; from.setLocation(icx + Math.cos(a1) * radius, icy + Math.sin(a1) * radius); to.setLocation(icx + Math.cos(a2) * radius, icy + Math.sin(a2) * radius); } } if (from.getX() == to.getX() && from.getY() == to.getY()) return 1; return 2; } //------------------------------- PolyMerge Interface ------------------------------- /** * Method to satisfy the PolyMerge interface by return the polygon (this object). * @return this object. */ public PolyBase getPolygon() {return this;} }