/* * (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.geom.AffineTransform; import java.util.List; import java.util.Vector; import java.util.logging.Logger; import org.openflexo.fge.geom.area.FGEArea; import org.openflexo.fge.geom.area.FGEEmptyArea; import org.openflexo.fge.geom.area.FGEExclusiveOrArea; import org.openflexo.fge.geom.area.FGEHalfLine; 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>FGEPolylin</code> class encapsulates a description of an open path, defined as an arbitrary number of line segments * * @author sylvain * */ public class FGEPolylin implements FGEGeometricObject<FGEPolylin> { private static final Logger logger = Logger.getLogger(FGEPolylin.class.getPackage().getName()); protected Vector<FGEPoint> _points; protected Vector<FGESegment> _segments; private FGERectangle bounds; public FGEPolylin() { super(); _points = new Vector<FGEPoint>(); _segments = new Vector<FGESegment>(); } public FGEPolylin(List<FGEPoint> points) { this(); for (FGEPoint p : points) { addToPoints(p); } } public FGEPolylin(FGEPoint... points) { this(); for (FGEPoint p : points) { addToPoints(p); } } @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)); _segments.add(s2); } reCalculateBounds(); } public void removeFromPoints(FGEPoint aPoint) { _points.remove(aPoint); reCalculateBounds(); } public void removePointAtIndex(int index) { _points.remove(index); if (index == getSegmentNb()) { // Last segment _segments.remove(index - 1); } else { if (index < _segments.size()) { _segments.remove(index); } if (index >= 1 && index - 1 < _segments.size() && index < _points.size()) { _segments.elementAt(index - 1).setP2(_points.elementAt(index)); } } boundsChanged(); } public void insertPointAtIndex(FGEPoint aPoint, int index) { _points.add(index, aPoint); if (index >= 1 && index - 1 < _segments.size()) { _segments.get(index - 1).setP2(aPoint); } if (index + 1 < _points.size()) { _segments.add(index, new FGESegment(aPoint, _points.get(index + 1))); } boundsChanged(); } public void updatePointAt(int index, FGEPoint aPoint) { _points.elementAt(index).setX(aPoint.x); _points.elementAt(index).setY(aPoint.y); if (getPointsNb() > 1) { if (index < _segments.size()) { _segments.elementAt(index).setP1(aPoint); } if (index > 0) { _segments.elementAt(index - 1).setP2(aPoint); } } boundsChanged(); } public int getPointsNb() { return _points.size(); } public FGEPoint getPointAt(int index) { if (index >= 0 && index < _points.size()) { return _points.elementAt(index); } return null; } public FGEPoint getFirstPoint() { return getPointAt(0); } public FGEPoint getLastPoint() { return getPointAt(getPointsNb() - 1); } public int getPointIndex(FGEPoint point) { int index = 0; for (FGEPoint p : getPoints()) { if (p.equals(point)) { return index; } index++; } return -1; } public Vector<FGESegment> getSegments() { return _segments; } public int getSegmentNb() { return _segments.size(); } public FGESegment getSegmentAt(int index) { if (index >= 0 && index < _segments.size()) { return _segments.elementAt(index); } return null; } public int getSegmentIndex(FGESegment segment) { int index = 0; for (FGESegment s : getSegments()) { if (s.equals(segment)) { return index; } index++; } return -1; } public FGESegment getFirstSegment() { return getSegmentAt(0); } public FGESegment getLastSegment() { return getSegmentAt(getSegmentNb() - 1); } public FGESegment getBiggestSegment() { double length = 0; FGESegment returned = null; for (FGESegment seg : getSegments()) { if (seg.getLength() > length) { length = seg.getLength(); returned = seg; } } return returned; } 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.FILLED); boundsChanged = false; } public FGERectangle getBoundingBox() { if (boundsChanged || bounds == null) { reCalculateBounds(); } return bounds; } private boolean boundsChanged = false; public void boundsChanged() { boundsChanged = true; } @Override public boolean containsLine(FGEAbstractLine l) { if (l instanceof FGESegment) { for (FGESegment s : _segments) { if (s.containsLine(l)) { return true; } } } return false; } public boolean contains(double x, double y) { return containsPoint(new FGEPoint(x, y)); } /** * 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 FGEPolylin clone() { try { return (FGEPolylin) 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); } 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 } } } return returned; } /** * Return the closest segment of supplied point * * @return */ public FGESegment getNearestSegment(FGEPoint p) { double shortestDistance = Double.POSITIVE_INFINITY; FGESegment returned = null; for (FGESegment s : getSegments()) { double distance = FGEPoint.distance(p, s.getNearestPoint(p)); if (distance <= shortestDistance) { returned = s; shortestDistance = distance; } } return returned; } public double getRelativeLocation(FGEPoint p) { double cumulated = 0; FGESegment s = getNearestSegment(p); FGEPoint proj = s.getNearestPointOnSegment(p); int index = getSegmentIndex(s); for (int i = 0; i < index; i++) { cumulated += getSegmentAt(i).getLength(); } cumulated += s.getLength() * s.getRelativeLocation(proj); return cumulated / getLength(); } 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.add(intersection); } } } catch (ParallelLinesException e) { // don't care } } return FGEUnionArea.makeUnion(crossed); } private FGEArea computePolylinIntersection(FGEPolylin polylin) { /*logger.info("computePolylinIntersection()"); logger.info("polylin1="+this); logger.info("polylin2="+polylin);*/ Vector<FGEArea> unionAreas = new Vector<FGEArea>(); for (FGESegment s1 : getSegments()) { for (FGESegment s2 : polylin.getSegments()) { FGEArea i = s1.intersect(s2); if (!(i instanceof FGEEmptyArea)) { unionAreas.add(i); } } } // logger.info("return="+FGEUnionArea.makeUnion(unionAreas)); return FGEUnionArea.makeUnion(unionAreas); } @Override public FGEArea exclusiveOr(FGEArea area) { return new FGEExclusiveOrArea(this, area); } @Override public FGEArea intersect(FGEArea 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 FGEPolylin) { return computePolylinIntersection((FGEPolylin) 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 final FGEArea union(FGEArea area) { if (containsArea(area)) { return clone(); } if (area.containsArea(this)) { return area.clone(); } if (area instanceof FGEPolylin) { FGEPolylin p = (FGEPolylin) area; // logger.info("Union of "+this+" and "+p); FGEArea returned = clone(); for (FGESegment s : p.getSegments()) { returned = returned.union(s); } if (returned instanceof FGEUnionArea) { return new FGEUnionArea(this, area); } return returned; } if (area instanceof FGESegment) { FGEPolylin clone = clone(); if (containsArea(area)) { return clone; } FGESegment s = (FGESegment) area; if (s.getP1().equals(getFirstPoint())) { if (s.getP2().equals(getLastPoint())) { return FGEPolygon.makeArea(Filling.NOT_FILLED, getPoints()); // return new FGEPolygon(Filling.NOT_FILLED,getPoints()); } clone.insertPointAtIndex(s.getP2(), 0); return clone; } else if (s.getP2().equals(getFirstPoint())) { if (s.getP1().equals(getLastPoint())) { return FGEPolygon.makeArea(Filling.NOT_FILLED, getPoints()); // return new FGEPolygon(Filling.NOT_FILLED,getPoints()); } clone.insertPointAtIndex(s.getP1(), 0); return clone; } else if (s.getP1().equals(getLastPoint())) { if (s.getP2().equals(getFirstPoint())) { return FGEPolygon.makeArea(Filling.NOT_FILLED, getPoints()); // return new FGEPolygon(Filling.NOT_FILLED,getPoints()); } clone.addToPoints(s.getP2()); return clone; } else if (s.getP2().equals(getLastPoint())) { if (s.getP1().equals(getFirstPoint())) { return FGEPolygon.makeArea(Filling.NOT_FILLED, getPoints()); // return new FGEPolygon(Filling.NOT_FILLED,getPoints()); } clone.addToPoints(s.getP1()); return clone; } } return new FGEUnionArea(this, area); } @Override public boolean containsPoint(FGEPoint p) { for (FGESegment s : _segments) { if (s.containsPoint(p)) { return true; } } return false; } @Override public boolean containsArea(FGEArea a) { if (a instanceof FGESegment && _segments.contains(a)) { return true; } if (a instanceof FGEPolylin) { boolean allContained = true; for (FGESegment s : ((FGEPolylin) a).getSegments()) { if (!containsArea(s)) { allContained = false; break; } } if (allContained) { return true; } } for (FGESegment s : _segments) { if (s.containsArea(a)) { return true; } } if (a instanceof FGEShape) { return FGEShape.AreaComputation.isShapeContainedInArea((FGEShape<?>) a, this); } return false; } @Override public FGEPolylin transform(AffineTransform t) { Vector<FGEPoint> points = new Vector<FGEPoint>(); for (FGEPoint p : _points) { points.add(p.transform(t)); } FGEPolylin returned = new FGEPolylin(points); return returned; } @Override public void paint(FGEGraphics g) { g.useDefaultForegroundStyle(); for (FGESegment s : _segments) { s.paint(g); } } @Override public String toString() { return "FGEPolylin: " + _points; } @Override public String getStringRepresentation() { return toString(); } @Override public boolean equals(Object obj) { if (obj instanceof FGEPolylin) { FGEPolylin p = (FGEPolylin) obj; if (getPointsNb() != p.getPointsNb()) { return false; } boolean isEquals = true; // Test in same order for (int i = 0; i < getPointsNb(); i++) { if (!getPointAt(i).equals(p.getPointAt(i))) { isEquals = false; } } if (!isEquals) { isEquals = true; // Test in reverse order for (int i = 0; i < getPointsNb(); i++) { if (!getPointAt(i).equals(p.getPointAt(getPointsNb() - 1 - i))) { isEquals = false; } } } return isEquals; } return super.equals(obj); } public double getLength() { double returned = 0; for (FGESegment s : getSegments()) { returned += s.getLength(); } return returned; } public FGEPoint getMiddle() { return getPointAtRelativePosition(0.5); } public FGEPoint getPointAtRelativePosition(double position) { double middleDistancePath = getLength() * position; double distance = 0; for (FGESegment s : getSegments()) { if (distance <= middleDistancePath && distance + s.getLength() >= middleDistancePath) { double ratio = (middleDistancePath - distance) / s.getLength(); FGEPoint p = new FGEPoint(); p.x = s.getP1().x + (s.getP2().x - s.getP1().x) * ratio; p.y = s.getP1().y + (s.getP2().y - s.getP1().y) * ratio; return p; } distance += s.getLength(); } logger.warning("Unexpected situation while computing relative position of polylin"); return new FGEPoint(0, 0); } @Override public FGEArea getOrthogonalPerspectiveArea(SimplifiedCardinalDirection orientation) { Vector<FGEArea> allAreas = new Vector<FGEArea>(); for (FGESegment s : getSegments()) { allAreas.add(s.getOrthogonalPerspectiveArea(orientation)); } return FGEUnionArea.makeUnion(allAreas); } @Override public FGEArea getAnchorAreaFrom(SimplifiedCardinalDirection orientation) { return computeVisibleSegmentsFrom(orientation, getSegments()); // 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); // 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) / OK 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(); } public static FGEArea computeVisibleSegmentsFrom(SimplifiedCardinalDirection orientation, Vector<FGESegment> segments) { // 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 : segments) { FGEHalfLine hl = FGEHalfLine.makeHalfLine(s.getMiddle(), orientation); // Test if this half-line "cuts" an other segment boolean cutsAnOtherSegment = false; for (FGESegment s2 : segments) { 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 { return FGEUnionArea.makeUnion(keptSegments); // 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);*/ } } }