Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PolyBase.java
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* 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.technology.Layer;
import com.sun.electric.tool.Job;
import com.sun.electric.util.math.AbstractFixpPoint;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.ECoord;
import com.sun.electric.util.math.FixpCoord;
import com.sun.electric.util.math.FixpRectangle;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;
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 Point points[];
/** the layer (used for graphics) */
private Layer layer;
/** the bounds of the points */
protected FixpRectangle 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;
public static class Point extends AbstractFixpPoint {
private long fixpX;
private long fixpY;
/**
* Constructs and initializes a Point with the
* specified coordinates.
*
* @param fixpX the X coordinate of the newly constructed Point
* @param fixpY the Y coordinate of the newly constructed Point
*/
private Point(long fixpX, long fixpY) {
this.fixpX = fixpX;
this.fixpY = fixpY;
}
@Override
public long getFixpX() {
return fixpX;
}
@Override
public long getFixpY() {
return fixpY;
}
@Override
public void setFixpLocation(long fixpX, long fixpY) {
this.fixpX = fixpX;
this.fixpY = fixpY;
}
@Override
protected AbstractFixpPoint create(long fixpX, long fixpY) {
return new Point(fixpX, fixpY);
}
}
/**
* Constructs and initializes a PolyBase.Point with the
* specified Point2D.
*
* @param p coordintates of the newly constructed Point
*/
public static Point from(Point2D p) {
if (p instanceof AbstractFixpPoint) {
AbstractFixpPoint fp = (AbstractFixpPoint)p;
return fromFixp(fp.getFixpX(), fp.getFixpY());
}
return fromLambda(p.getX(), p.getY());
}
/**
* Constructs and initializes a PolyBase.Point with the
* specified coordinates.
*
* @param x the X coordinate of the newly constructed Point
* @param y the Y coordinate of the newly constructed Point
*/
public static Point from(ECoord x, ECoord y) {
return new Point(x.getFixp(), y.getFixp());
}
/**
* Constructs and initializes a PolyBase.Point with the
* specified lambda coordinates.
*
* @param lambdaX the X coordinate of the newly constructed Point
* @param lambdaY the Y coordinate of the newly constructed Point
*/
public static Point fromLambda(double lambdaX, double lambdaY) {
return new Point(FixpCoord.lambdaToFixp(lambdaX), FixpCoord.lambdaToFixp(lambdaY));
}
/**
* Constructs and initializes a PolyBase.Point with the
* specified fixed-point long coordinates.
*
* @param fixpX the X coordinate of the newly constructed Point
* @param fixpY the Y coordinate of the newly constructed Point
*/
public static Point fromFixp(long fixpX, long fixpY) {
return new Point(fixpX, fixpY);
}
/**
* Constructs and initializes a PolyBase.Point with the
* specified grid coordinates.
*
* @param gridX the X coordinate of the newly constructed Point
* @param gridY the Y coordinate of the newly constructed Point
*/
public static Point fromGrid(long gridX, long gridY) {
return new Point(gridX << FixpCoord.FRACTION_BITS, gridY << FixpCoord.FRACTION_BITS);
}
/**
* The constructor creates a new Poly given an array of points.
* @param points the array of coordinates.
*/
public PolyBase(Point... 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 Point[] makePoints(double lX, double hX, double lY, double hY) {
return new Point[]{
fromLambda(lX, lY),
fromLambda(hX, lY),
fromLambda(hX, hY),
fromLambda(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 Point[] makePoints(Rectangle2D rect) {
double lX = rect.getMinX();
double hX = rect.getMaxX();
double lY = rect.getMinY();
double hY = rect.getMaxY();
return new Point[]{
fromLambda(lX, lY),
fromLambda(hX, lY),
fromLambda(hX, hY),
fromLambda(lX, hY)};
}
/**
* Method to help initialize this Poly.
*/
private void initialize(Point[] 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
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
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 Point[] 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(FixpTransform af) {
// Nothing to do
if (af.getType() == FixpTransform.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;
bitRectangle = 2; // Not calcuylated
}
/**
* 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 FixpRectangle 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].getFixpX() != points[4].getFixpX() || points[0].getFixpY() != points[4].getFixpY()) {
return null;
}
} else {
return null;
}
// make sure the polygon is rectangular and orthogonal
if (points[0].getFixpX() == points[1].getFixpX() && points[2].getFixpX() == points[3].getFixpX()
&& points[0].getFixpY() == points[3].getFixpY() && points[1].getFixpY() == points[2].getFixpY()) {
bitRectangle = 1;
return getBounds2D();
}
if (points[0].getFixpX() == points[3].getFixpX() && points[1].getFixpX() == points[2].getFixpX()
&& points[0].getFixpY() == points[1].getFixpY() && points[2].getFixpY() == points[3].getFixpY()) {
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 algorithm 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 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;
}
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);
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();
// 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();
// do not reduce in X if arc is horizontal
if (angle != -1 && 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 != -1 && 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());
FixpTransform 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) {
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;
}
// 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());
FixpTransform 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()
&& ((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 {
Point[] savePoints = points;
clipArc(otherBounds.getMinX(), otherBounds.getMaxX(), otherBounds.getMinY(), otherBounds.getMaxY());
Point[] 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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 getIntersection(PolyBase polyOther, List overlappingEdges) {
// get overlapping edges if requested
if (overlappingEdges != null) {
List overlaps = getOverlappingEdges(polyOther);
overlappingEdges.addAll(overlaps);
}
// get intersected area
Area myArea = new Area(this);
Area otherArea = new Area(polyOther);
myArea.intersect(otherArea);
List 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 getOverlappingEdges(PolyBase polyOther) {
List overlappingSegs = new ArrayList();
// 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 myLineSegs = getLineSegments();
List 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 realLines = new ArrayList();
List points = new ArrayList();
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 getLineSegments() {
List lineSegs = new ArrayList();
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);
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.
*/
@Override
public FixpRectangle 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.
*/
@Override
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) {
long cX = points[0].getFixpX();
long cY = points[0].getFixpY();
long radius = FixpCoord.lambdaToFixp(points[0].distance(points[1]));
bounds = FixpRectangle.fromFixpDiagonal(cX - radius, cY - radius, cX + radius, cY + radius);
return;
}
if (style == Poly.Type.CIRCLEARC || style == Poly.Type.THICKCIRCLEARC) {
bounds = FixpRectangle.from(GenMath.arcBBox(points[1], points[2], points[0]));
return;
}
if (points.length > 0) {
long lX = points[0].getFixpX();
long hX = lX;
long lY = points[0].getFixpY();
long hY = lY;
for (int i = 1; i < points.length; i++) {
long x = points[i].getFixpX();
long y = points[i].getFixpY();
if (x < lX) {
lX = x;
}
if (x > hX) {
hX = x;
}
if (y < lY) {
lY = y;
}
if (y > hY) {
hY = y;
}
}
bounds = FixpRectangle.fromFixpDiagonal(lX, lY, hX, hY);
} else {
bounds = FixpRectangle.fromFixpDiagonal(0, 0, 0, 0);
}
}
/**
* 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 getSortedLoops()
// {
// Collection set = getPointsInArea(new Area(this), layer, true, false, null);
// List list = new ArrayList(set);
// Collections.sort(list, AREA_COMPARATOR);
// return (list);
// }
/**
* Class to compare PolyBase objects
*/
private static Comparator AREA_COMPARATOR = new Comparator() {
/**
* 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.
*/
@Override
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 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 pointList = new ArrayList();
Point lastMoveTo = null;
List toDelete = new ArrayList();
List polyList = new ArrayList();
// 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);
}
PolyBase poly = new PolyBase(pointList.toArray(new Point[pointList.size()]));
// 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])) {
poly = new PolyBase(pn.getPoints().clone()); // poly is lost ??
// 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) {
Point pt = fromLambda(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 interface PolyBaseTree {
public Iterable getSons();
public PolyBase getPoly();
}
public static class PolyBaseTreeImpl implements PolyBaseTree {
List sons;
PolyBase poly;
public PolyBaseTreeImpl(PolyBase p) {
if (p == null) {
throw new NullPointerException();
}
poly = p;
// sons = new ArrayList();
}
@Override
public Iterable getSons() {
if (sons != null) {
return sons;
}
return Collections.emptyList();
}
@Override
public PolyBase getPoly() {
return poly;
}
void getLoops(int level, Stack stack) {
// Starting of a new polygon
if (level % 2 == 0) {
stack.push(poly);
} else {
PolyBase top = stack.pop();
Point[] points = new Point[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] = (Point) top.getPoints()[0].clone();
System.arraycopy(poly.getPoints(), 0, points, top.getPoints().length + 1, poly.getPoints().length);
points[points.length - 1] = (Point) 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++;
if (sons != null) {
for (PolyBaseTree t : sons) {
((PolyBaseTreeImpl) t).getLoops(level, stack);
}
}
}
boolean add(PolyBaseTreeImpl t) {
if (!poly.contains(t.poly.getPoints()[0])) {
// Belong to another root
return false;
}
if (sons == null || sons.size() == 0) {
double a = poly.getArea();
double b = t.poly.getArea();
addSonLowLevel(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) {
PolyBaseTreeImpl bi = (PolyBaseTreeImpl) b;
PolyBase pn = bi.poly;
if (pn.contains(t.poly.getPoints()[0])) {
return (bi.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;
}
public void addSonLowLevel(PolyBaseTree son) {
if (sons == null) {
sons = new ArrayList();
}
sons.add(son);
}
}
// This assumes the algorithm starts with external loop
public static List getPolyTrees(Area area, Layer layer) {
List list = getLoopsFromArea(area, layer);
List roots = getTreesFromLoops(list);
return roots;
}
// Get trees from loops
public static List getTreesFromLoops(List list) {
List roots = new ArrayList();
// areas are sorted from min to max
// Build the hierarchy with loops
for (int i = list.size() - 1; i > -1; i--) {
PolyBaseTreeImpl t = new PolyBaseTreeImpl(list.get(i));
// Check all possible roots
boolean added = false;
for (PolyBaseTree r : roots) {
if (((PolyBaseTreeImpl) r).add(t)) {
added = true;
break;
}
}
if (!added) {
roots.add(t);
}
}
return roots;
}
// Get Loops
public static List getLoopsFromArea(Area area, Layer layer) {
if (area == null) {
return null;
}
double[] coords = new double[6];
List pointList = new ArrayList();
List list = new ArrayList();
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) {
PolyBase poly = new PolyBase(pointList.toArray(new Point[pointList.size()]));
// 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) {
Point pt = fromLambda(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 getPointsFromComplex(Area area, Layer layer) {
List list = getLoopsFromArea(area, layer);
List 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 s = new Stack();
((PolyBaseTreeImpl) 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;
}
@Override
public int getWindingRule() {
return WIND_EVEN_ODD;
}
@Override
public boolean isDone() {
return idx > points.length;
}
@Override
public void next() {
idx++;
}
@Override
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);
}
@Override
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.
*/
@Override
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.
*/
@Override
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();
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, 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;
}
// 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) {
// it covers in X...do the crop
if (!DBMath.isGreaterThan(hY, uY)) {
hY = bY;
}
if (blY) {
lY = uY;
}
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) {
// it covers in Y...crop in X
if (!DBMath.isGreaterThan(hX, uX)) {
hX = bX;
}
if (blX) {
lX = uX;
}
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 Point[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 curveList = new ArrayList();
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 Point[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 Point[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 Point[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 Point[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 Point[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 newIn = new ArrayList();
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(fromLambda(xc, yc));
newIn.add(fromLambda(alP.x, alP.y));
newIn.add(fromLambda(al.x, al.y));
}
points = newIn.toArray(new Point[newIn.size()]);
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 {
@Override
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.
*/
@Override
public PolyBase getPolygon() {
return this;
}
}