/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.fge.geom; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Random; import java.util.Vector; import java.util.logging.Logger; import org.openflexo.fge.geom.area.FGEArea; import org.openflexo.fge.geom.area.FGEBand; import org.openflexo.fge.geom.area.FGEEmptyArea; import org.openflexo.fge.geom.area.FGEExclusiveOrArea; import org.openflexo.fge.geom.area.FGEHalfBand; import org.openflexo.fge.geom.area.FGEHalfLine; import org.openflexo.fge.geom.area.FGEHalfPlane; import org.openflexo.fge.geom.area.FGEIntersectionArea; import org.openflexo.fge.geom.area.FGESubstractionArea; import org.openflexo.fge.geom.area.FGEUnionArea; import org.openflexo.fge.graphics.FGEGraphics; /** * The <code>FGEPolygon</code> class encapsulates a description of a closed, two-dimensional region within a coordinate space. This region * is bounded by an arbitrary number of line segments, each of which is one side of the polygon. * * Some parts of this code are "inspired" from java.awt.Polygon implementation * * @author sylvain * */ public class FGEPolygon implements FGEGeometricObject<FGEPolygon>, FGEShape<FGEPolygon> { private static final Logger logger = Logger.getLogger(FGEPolygon.class.getPackage().getName()); protected Filling _filling; protected Vector<FGEPoint> _points; protected Vector<FGESegment> _segments; private FGERectangle bounds; public FGEPolygon() { this(Filling.NOT_FILLED); } public FGEPolygon(Filling filling) { super(); _filling = filling; _points = new Vector<FGEPoint>(); _segments = new Vector<FGESegment>(); } public FGEPolygon(Filling filling, List<FGEPoint> points) { this(filling); if (points != null) { for (FGEPoint p : points) { addToPoints(p); } } } public FGEPolygon(Filling filling, FGEPoint... points) { this(filling); for (FGEPoint p : points) { addToPoints(p); } } @Override public boolean getIsFilled() { return _filling == Filling.FILLED; } @Override public void setIsFilled(boolean filled) { _filling = filled ? Filling.FILLED : Filling.NOT_FILLED; } @Override public FGEPoint getCenter() { if (_points.size() == 0) { return new FGEPoint(0, 0); } double sumX = 0; double sumY = 0; for (FGEPoint p : _points) { sumX += p.x; sumY += p.y; } return new FGEPoint(sumX / _points.size(), sumY / _points.size()); } @Override public List<FGEPoint> getControlPoints() { return getPoints(); } public void clearPoints() { _points.clear(); _segments.clear(); } public Vector<FGEPoint> getPoints() { return _points; } public void setPoints(Vector<FGEPoint> points) { _points.clear(); _segments.clear(); for (FGEPoint p : points) { addToPoints(p); } } public void addToPoints(FGEPoint aPoint) { _points.add(aPoint); if (_points.size() > 1) { FGESegment s2 = new FGESegment(_points.elementAt(_points.size() - 2), _points.elementAt(_points.size() - 1)); if (_segments.size() <= _points.size() - 2) { _segments.add(s2); } else { _segments.set(_points.size() - 2, s2); } FGESegment s3 = new FGESegment(_points.elementAt(_points.size() - 1), _points.elementAt(0)); _segments.add(s3); } reCalculateBounds(); } public void removeFromPoints(FGEPoint aPoint) { _points.remove(aPoint); reCalculateBounds(); } public Vector<FGESegment> getSegments() { return _segments; } public int getPointsNb() { return _points.size(); } public FGEPoint getPointAt(int index) { return _points.elementAt(index); } public void geometryChanged() { reCalculateBounds(); } private void reCalculateBounds() { double boundsMinX = Double.POSITIVE_INFINITY; double boundsMinY = Double.POSITIVE_INFINITY; double boundsMaxX = Double.NEGATIVE_INFINITY; double boundsMaxY = Double.NEGATIVE_INFINITY; for (int i = 0; i < getPointsNb(); i++) { FGEPoint p = getPointAt(i); double x = p.getX(); boundsMinX = Math.min(boundsMinX, x); boundsMaxX = Math.max(boundsMaxX, x); double y = p.getY(); boundsMinY = Math.min(boundsMinY, y); boundsMaxY = Math.max(boundsMaxY, y); } bounds = new FGERectangle(boundsMinX, boundsMinY, boundsMaxX - boundsMinX, boundsMaxY - boundsMinY, _filling); } @Override public FGERectangle getBoundingBox() { return bounds; } @Override public boolean containsLine(FGEAbstractLine l) { if (l instanceof FGEHalfLine) { return false; } if (l instanceof FGESegment) { return containsPoint(l.getP1()) && containsPoint(l.getP2()); } return false; } @Override public boolean contains(double x, double y) { FGEPoint pt = new FGEPoint(x, y); for (FGESegment s : getSegments()) { if (s.contains(pt)) { return true; } } if (!getIsFilled()) { return false; } // Otherwise test on inside if (getPointsNb() <= 2 || !bounds.contains(x, y)) { return false; } int hits = 0; FGEPoint lastPoint = getPointAt(getPointsNb() - 1); double lastx = lastPoint.getX(); double lasty = lastPoint.getY(); FGEPoint currentPoint; double curx, cury; // Walk the edges of the polygon for (int i = 0; i < getPointsNb(); lastx = curx, lasty = cury, i++) { currentPoint = getPointAt(i); curx = currentPoint.getX(); cury = currentPoint.getY(); if (cury == lasty) { continue; } double leftx; if (curx < lastx) { if (x >= lastx) { continue; } leftx = curx; } else { if (x >= curx) { continue; } leftx = lastx; } double test1, test2; if (cury < lasty) { if (y < cury || y >= lasty) { continue; } if (x < leftx) { hits++; continue; } test1 = x - curx; test2 = y - cury; } else { if (y < lasty || y >= cury) { continue; } if (x < leftx) { hits++; continue; } test1 = x - lastx; test2 = y - lasty; } if (test1 < test2 / (lasty - cury) * (lastx - curx)) { hits++; } } return (hits & 1) != 0; } /** * Creates a new object of the same class and with the same contents as this object. * * @return a clone of this instance. * @exception OutOfMemoryError * if there is not enough memory. * @see java.lang.Cloneable * @since 1.2 */ @Override public FGEPolygon clone() { try { return (FGEPolygon) super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } @Override public FGEPoint getNearestPoint(FGEPoint aPoint) { return nearestOutlinePoint(aPoint); } @Override public FGEPoint nearestOutlinePoint(FGEPoint aPoint) { FGEPoint returnedPoint = null; double smallestDistance = Double.POSITIVE_INFINITY; for (FGESegment segment : _segments) { double sqDistanceToSegment = segment.ptSegDistSq(aPoint); if (sqDistanceToSegment < smallestDistance) { returnedPoint = segment.getNearestPointOnSegment(aPoint); smallestDistance = sqDistanceToSegment; } } return returnedPoint; } /** * Return nearest point from point "from" following supplied orientation * * Returns null if no intersection was found * * @param from * point from which we are coming to area * @param orientation * orientation we are coming from * @return */ @Override public FGEPoint nearestPointFrom(FGEPoint from, SimplifiedCardinalDirection orientation) { FGEHalfLine hl = FGEHalfLine.makeHalfLine(from, orientation); FGEPoint returned = null; double minimalDistanceSq = Double.POSITIVE_INFINITY; for (FGESegment segment : _segments) { if (FGESegment.intersectsInsideSegment(segment, hl)) { try { FGEPoint p = FGEAbstractLine.getLineIntersection(segment, hl); double distSq = FGEPoint.distanceSq(from, p); if (distSq < minimalDistanceSq) { returned = p; minimalDistanceSq = distSq; } } catch (ParallelLinesException e) { // Don't care } } } // logger.info("from: "+from+" orientation="+orientation+" return "+returned); return returned; } private FGEArea computeAreaIntersection(FGEArea area) { // System.out.println("Intersection between "+this+" and "+area); boolean fullyInside = true; boolean fullyOutside = true; for (FGESegment s : getSegments()) { if (!area.containsArea(s)) { fullyInside = false; } if (area.containsPoint(s.getP1()) || area.containsPoint(s.getP2())) { fullyOutside = false; } } if (fullyOutside) { // System.out.println("Fully outside"); return new FGEEmptyArea(); } if (fullyInside) { // System.out.println("Fully inside"); return this.clone(); } // Otherwise non null intersection FGERectangle filledBoundingBox = getBoundingBox().clone(); filledBoundingBox.setIsFilled(true); FGEArea boundingIntersect = area.intersect(filledBoundingBox); // System.out.println("Y'a une intersection zarrebi: boundingIntersect="+boundingIntersect+" resultat: "+intersect(boundingIntersect)); return intersect(boundingIntersect); } private FGEArea computeLineIntersection(FGEAbstractLine line) { Vector<FGEPoint> crossed = new Vector<FGEPoint>(); for (FGESegment s : _segments) { if (line.overlap(s)) { return s.clone(); // TODO: perform union of potential multiple overlaping segments } try { if (s.intersectsInsideSegment(line)) { FGEPoint intersection = s.getLineIntersection(line); if (line.contains(intersection) && (crossed.size() == 0 || !crossed.lastElement().equals(intersection))) { crossed.add(intersection); } } } catch (ParallelLinesException e) { // don't care } } if (crossed.size() == 0) { return new FGEEmptyArea(); } if (crossed.size() == 1) { return crossed.firstElement(); } else if (crossed.size() == 2) { if (getIsFilled()) { return new FGESegment(crossed.firstElement(), crossed.elementAt(1)); } else { return FGEUnionArea.makeUnion(crossed.firstElement(), crossed.elementAt(1)); } } else { // TODO: not yet implemented for filled polygon logger.warning("computeLineIntersection() not yet implemented for polygon"); return FGEUnionArea.makeUnion(crossed); } } @Override public FGEArea exclusiveOr(FGEArea area) { return new FGEExclusiveOrArea(this, area); } @Override public FGEArea intersect(FGEArea area) { // logger.info("Polygon "+this+" intersect with "+area); if (area.containsArea(this)) { return this.clone(); } if (containsArea(area)) { return area.clone(); } if (area instanceof FGEAbstractLine) { return computeLineIntersection((FGEAbstractLine) area); } if (area instanceof FGERectangle) { return ((FGERectangle) area).intersect(this); } if (area instanceof FGEHalfPlane) { return ((FGEHalfPlane) area).intersect(this); } if (area instanceof FGEPolygon) { return FGEShape.AreaComputation.computeShapeIntersection(this, (FGEPolygon) area); } if (area instanceof FGEBand) { return computeAreaIntersection(area); } if (area instanceof FGEHalfBand) { return computeAreaIntersection(area); } FGEIntersectionArea returned = new FGEIntersectionArea(this, area); if (returned.isDevelopable()) { return returned.makeDevelopped(); } else { return returned; } } @Override public FGEArea substract(FGEArea area, boolean isStrict) { return new FGESubstractionArea(this, area, isStrict); } @Override public FGEArea union(FGEArea area) { if (containsArea(area)) { return clone(); } if (area.containsArea(this)) { return area.clone(); } return new FGEUnionArea(this, area); } @Override public boolean containsPoint(FGEPoint p) { return contains(p.getX(), p.getY()); } @Override public boolean containsArea(FGEArea a) { if (a instanceof FGEPoint) { return containsPoint((FGEPoint) a); } if (a instanceof FGESegment) { return containsPoint(((FGESegment) a).getP1()) && containsPoint(((FGESegment) a).getP2()); } if (a instanceof FGEShape) { return FGEShape.AreaComputation.isShapeContainedInArea((FGEShape<?>) a, this); } return false; } @Override public FGEPolygon transform(AffineTransform t) { Vector<FGEPoint> points = new Vector<FGEPoint>(); for (FGEPoint p : _points) { points.add(p.transform(t)); } FGEPolygon returned = new FGEPolygon(_filling, points); return returned; } @Override public void paint(FGEGraphics g) { if (getIsFilled()) { g.useDefaultBackgroundStyle(); g.fillPolygon(getPoints().toArray(new FGEPoint[getPoints().size()])); } g.useDefaultForegroundStyle(); g.drawPolygon(getPoints().toArray(new FGEPoint[getPoints().size()])); /* g.setDefaultBackground(BackgroundStyle.makeEmptyBackground()); g.setDefaultForeground(ForegroundStyle.makeStyle(Color.GRAY,1,DashStyle.MEDIUM_DASHES)); getBoundingBox().paint(g);*/ } @Override public String toString() { return "FGEPolygon: " + _points; } @Override public String getStringRepresentation() { return toString(); } // TODO: this algorithm is really not optimal since we explore all solution with a combinational algorithm !!! // As we stay with a small number of points, we keep it for now private static List<FGEPoint> sortToAvoidCuts(List<FGEPoint> aList) { for (FGEPoint p : aList) { if (Double.isNaN(p.x) || p.x == Double.POSITIVE_INFINITY || p.x == Double.NEGATIVE_INFINITY) { return aList; // On laisse tomber } if (Double.isNaN(p.y) || p.y == Double.POSITIVE_INFINITY || p.y == Double.NEGATIVE_INFINITY) { return aList; // On laisse tomber } } return sortToAvoidCuts(new Vector<FGEPoint>(), aList); } private static List<FGEPoint> sortToAvoidCuts(List<FGEPoint> aList, List<FGEPoint> remainingPoints) { if (remainingPoints.size() == 0) { return aList; } for (FGEPoint newP : remainingPoints) { Vector<FGESegment> sl = new Vector<FGESegment>(); FGEPoint previous = null; for (FGEPoint p : aList) { if (previous != null) { sl.add(new FGESegment(previous, p)); } previous = p; } boolean thisPointMightBeGood = true; if (sl.size() > 0) { // System.out.println("Segments = "+sl); FGESegment newSegment = new FGESegment(aList.get(aList.size() - 1), newP); for (FGESegment oldS : sl) { if (oldS.intersectsInsideSegment(newSegment, true)) { thisPointMightBeGood = false; // System.out.println("Failed because new segment "+newSegment+" intersect with segment "+oldS); } } if (remainingPoints.size() == 1) { // This is the last point, we must also check closure FGESegment closure = new FGESegment(newP, aList.get(0)); // System.out.println("Also check closure = "+closure); for (FGESegment oldS : sl) { if (oldS.intersectsInsideSegment(closure, true)) { thisPointMightBeGood = false; // System.out.println("Failed because closure "+closure+" intersect with segment "+oldS); } } } } if (thisPointMightBeGood) { Vector<FGEPoint> newList = new Vector<FGEPoint>(); newList.addAll(aList); newList.add(newP); Vector<FGEPoint> newRemainingList = new Vector<FGEPoint>(); newRemainingList.addAll(remainingPoints); newRemainingList.remove(newP); List<FGEPoint> returned = sortToAvoidCuts(newList, newRemainingList); if (returned != null) { // System.out.println("return "+returned); return returned; } } } // No point found, return return null; } public static void main(String[] args) { for (int n = 1; n < 100; n++) { Vector<FGEPoint> pts = new Vector<FGEPoint>(); logger.info("n=" + n); Random rand = new Random(); for (int i = 0; i < n; i++) { pts.add(new FGEPoint(rand.nextDouble(), rand.nextDouble())); } logger.info("resultat: " + sortToAvoidCuts(pts)); } } /** * Make a new area given a list of point May return a rectangle, a polygon or a segment * * @param filling * @param points * @return */ public static FGEArea makeArea(Filling filling, List<FGEPoint> somePoints) { if (somePoints.size() < 1) { throw new IllegalArgumentException("makeArea() called with " + somePoints.size() + " points"); } else if (somePoints.size() == 1) { return new FGEPoint(somePoints.get(0)); } else if (somePoints.size() == 2) { return new FGESegment(somePoints.get(0), somePoints.get(1)); } else { List<FGEPoint> points = sortToAvoidCuts(somePoints); if (points.size() == 4) { boolean isRectangle = true; double minx = Double.POSITIVE_INFINITY; double miny = Double.POSITIVE_INFINITY; double maxx = Double.NEGATIVE_INFINITY; double maxy = Double.NEGATIVE_INFINITY; for (int i = 0; i < points.size(); i++) { if (points.get(i).x < minx) { minx = points.get(i).x; } if (points.get(i).y < miny) { miny = points.get(i).y; } if (points.get(i).x > maxx) { maxx = points.get(i).x; } if (points.get(i).y > maxy) { maxy = points.get(i).y; } } for (int i = 0; i < points.size(); i++) { FGEPoint p = points.get(i); if (!((p.x == minx || p.x == maxx) && (p.y == miny || p.y == maxy))) { isRectangle = false; } } if (isRectangle) { if (maxx - minx == 0) { // width = 0 if (maxy - miny == 0) { return new FGEPoint(minx, miny); } else { return new FGESegment(minx, miny, minx, maxy); } } else { if (maxy - miny == 0) { return new FGESegment(minx, miny, maxx, miny); // height = 0; } else { return new FGERectangle(minx, miny, maxx - minx, maxy - miny, filling); } } } } return new FGEPolygon(filling, points); } } public static FGEArea makeArea(Filling filling, FGEPoint... points) { Vector<FGEPoint> v = new Vector<FGEPoint>(); for (FGEPoint p : points) { v.add(p); } return makeArea(filling, v); } @Override public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } @Override public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } @Override public boolean contains(double x, double y, double w, double h) { if (_points.size() <= 0 || !bounds.intersects(x, y, w, h)) { return false; } // Implement this; // Crossings cross = getCrossings(x, y, x+w, y+h); // return (cross != null && cross.covers(y, y+h)); return true; } @Override public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } @Override public boolean intersects(double x, double y, double w, double h) { // TODO Implement this // Crossings cross = getCrossings(x, y, x+w, y+h); // return (cross == null || !cross.isEmpty()); return false; } @Override public Rectangle getBounds() { return new Rectangle((int) bounds.getX(), (int) bounds.getY(), (int) bounds.getWidth(), (int) bounds.getHeight()); } @Override public Rectangle2D getBounds2D() { return getBounds(); } /** * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to the geometry of the * outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so that the coordinates returned in the * iteration are transformed accordingly. * * @param at * an optional <code>AffineTransform</code> to be applied to the coordinates as they are returned in the iteration, or * <code>null</code> if untransformed coordinates are desired * @return a {@link PathIterator} object that provides access to the geometry of this <code>Polygon</code>. */ @Override public PathIterator getPathIterator(AffineTransform at) { return new PolygonPathIterator(this, at); } /** * Returns an iterator object that iterates along the boundary of the <code>Shape</code> and provides access to the geometry of the * outline of the <code>Shape</code>. Only SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point types are returned by the iterator. Since * polygons are already flat, the <code>flatness</code> parameter is ignored. An optional <code>AffineTransform</code> can be specified * in which case the coordinates returned in the iteration are transformed accordingly. * * @param at * an optional <code>AffineTransform</code> to be applied to the coordinates as they are returned in the iteration, or * <code>null</code> if untransformed coordinates are desired * @param flatness * the maximum amount that the control points for a given curve can vary from colinear before a subdivided curve is replaced * by a straight line connecting the endpoints. Since polygons are already flat the <code>flatness</code> parameter is * ignored. * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry. */ @Override public PathIterator getPathIterator(AffineTransform at, double flatness) { return getPathIterator(at); } class PolygonPathIterator implements PathIterator { FGEPolygon poly; AffineTransform transform; int index; public PolygonPathIterator(FGEPolygon pg, AffineTransform at) { poly = pg; transform = at; if (pg.getPointsNb() == 0) { // Prevent a spurious SEG_CLOSE segment index = 1; } } /** * Returns the winding rule for determining the interior of the path. * * @return an integer representing the current winding rule. * @see PathIterator#WIND_NON_ZERO */ @Override public int getWindingRule() { return WIND_EVEN_ODD; } /** * Tests if there are more points to read. * * @return <code>true</code> if there are more points to read; <code>false</code> otherwise. */ @Override public boolean isDone() { return index > poly.getPointsNb(); } /** * Moves the iterator forwards, along the primary direction of traversal, to the next segment of the path when there are more points * in that direction. */ @Override public void next() { index++; } /** * Returns the coordinates and type of the current path segment in the iteration. The return value is the path segment type: * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE. A <code>float</code> array of length 2 must be passed in and can be used to store the * coordinates of the point(s). Each point is stored as a pair of <code>float</code> x, y coordinates. SEG_MOVETO and * SEG_LINETO types return one point, and SEG_CLOSE does not return any points. * * @param coords * a <code>float</code> array that specifies the coordinates of the point(s) * @return an integer representing the type and coordinates of the current path segment. * @see PathIterator#SEG_MOVETO * @see PathIterator#SEG_LINETO * @see PathIterator#SEG_CLOSE */ @Override public int currentSegment(float[] coords) { if (index >= poly.getPointsNb()) { return SEG_CLOSE; } FGEPoint p = poly.getPointAt(index); coords[0] = (float) p.x; coords[1] = (float) p.y; if (transform != null) { transform.transform(coords, 0, coords, 0, 1); } return index == 0 ? SEG_MOVETO : SEG_LINETO; } /** * Returns the coordinates and type of the current path segment in the iteration. The return value is the path segment type: * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE. A <code>double</code> array of length 2 must be passed in and can be used to store the * coordinates of the point(s). Each point is stored as a pair of <code>double</code> x, y coordinates. SEG_MOVETO and * SEG_LINETO types return one point, and SEG_CLOSE does not return any points. * * @param coords * a <code>double</code> array that specifies the coordinates of the point(s) * @return an integer representing the type and coordinates of the current path segment. * @see PathIterator#SEG_MOVETO * @see PathIterator#SEG_LINETO * @see PathIterator#SEG_CLOSE */ @Override public int currentSegment(double[] coords) { if (index >= poly.getPointsNb()) { return SEG_CLOSE; } FGEPoint p = poly.getPointAt(index); coords[0] = (float) p.x; coords[1] = (float) p.y; if (transform != null) { transform.transform(coords, 0, coords, 0, 1); } return index == 0 ? SEG_MOVETO : SEG_LINETO; } } @Override public boolean equals(Object obj) { if (obj instanceof FGEPolygon) { FGEPolygon p = (FGEPolygon) obj; if (getPointsNb() != p.getPointsNb()) { return false; } if (getIsFilled() != p.getIsFilled()) { return false; } // Test same order with different indexes for (int j = 0; j < getPointsNb(); j++) { boolean testWithIndexJ = true; for (int i = 0; i < getPointsNb(); i++) { int k = i + j; if (k >= getPointsNb()) { k = k - getPointsNb(); } if (!getPointAt(i).equals(p.getPointAt(k))) { testWithIndexJ = false; } } if (testWithIndexJ) { return true; } } // Test reverse order with different indexes for (int j = 0; j < getPointsNb(); j++) { boolean testWithIndexJ = true; for (int i = 0; i < getPointsNb(); i++) { int k = -i + j; if (k < 0) { k = k + getPointsNb(); } if (!getPointAt(i).equals(p.getPointAt(k))) { testWithIndexJ = false; } } if (testWithIndexJ) { return true; } } return false; } return super.equals(obj); } @Override public FGEArea getOrthogonalPerspectiveArea(SimplifiedCardinalDirection orientation) { return getAnchorAreaFrom(orientation).getOrthogonalPerspectiveArea(orientation); } @Override public FGEArea getAnchorAreaFrom(SimplifiedCardinalDirection orientation) { // This algorithm is not quite correct, you can find *very* pathologic cases, but works in most cases Vector<FGESegment> keptSegments = new Vector<FGESegment>(); for (FGESegment s : getSegments()) { FGEHalfLine hl = FGEHalfLine.makeHalfLine(s.getMiddle(), orientation); /*switch (orientation) { case NORTH: hl = new FGEHalfLine(s.getMiddle(),s.getMiddle().transform(AffineTransform.getTranslateInstance(0,-1))); break; case SOUTH: hl = new FGEHalfLine(s.getMiddle(),s.getMiddle().transform(AffineTransform.getTranslateInstance(0,1))); break; case EAST: hl = new FGEHalfLine(s.getMiddle(),s.getMiddle().transform(AffineTransform.getTranslateInstance(1,0))); break; case WEST: hl = new FGEHalfLine(s.getMiddle(),s.getMiddle().transform(AffineTransform.getTranslateInstance(-1,0))); break; default: break; }*/ // Test if this half-line "cuts" an other segment boolean cutsAnOtherSegment = false; for (FGESegment s2 : getSegments()) { if (!s.equals(s2)) { FGEArea intersect = s2.intersect(hl); if (intersect instanceof FGEPoint) { cutsAnOtherSegment = true; } else if (intersect instanceof FGEEmptyArea) { ; } else { logger.warning("Unexpected intersection: " + intersect); cutsAnOtherSegment = true; } } } if (!cutsAnOtherSegment) { keptSegments.add(s); } } if (keptSegments.size() == 0) { return new FGEEmptyArea(); } else if (keptSegments.size() == 1) { return keptSegments.firstElement(); } else { // Chains segments Vector<FGESegment> chain = new Vector<FGESegment>(); for (FGESegment s : keptSegments) { if (chain.size() == 0) { chain.add(s); } else { if (s.getP1().equals(chain.firstElement().getP1())) { chain.add(0, new FGESegment(s.getP2(), s.getP1())); } else if (s.getP2().equals(chain.firstElement().getP1())) { chain.add(0, s); } else if (s.getP1().equals(chain.lastElement().getP2())) { chain.add(s); } else if (s.getP2().equals(chain.lastElement().getP2())) { chain.add(new FGESegment(s.getP2(), s.getP1())); } else { logger.warning("Multiple chains not implemented yet"); } } } Vector<FGEPoint> pts = new Vector<FGEPoint>(); pts.add(chain.firstElement().getP1()); for (FGESegment s : chain) { pts.add(s.getP2()); } // logger.info("anchor area for "+orientation+" : "+new FGEPolylin(pts) ); return new FGEPolylin(pts); } } /** * This area is finite, so always return true */ @Override public final boolean isFinite() { return true; } /** * This area is finite, so always return null */ @Override public final FGERectangle getEmbeddingBounds() { return getBoundingBox(); } /** * Build and return new polylin representing outline * * @return */ public FGEPolylin getOutline() { Vector<FGEPoint> pts = new Vector<FGEPoint>(); pts.addAll(getPoints()); pts.add(getPoints().firstElement()); return new FGEPolylin(pts); } }