/* * (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.area; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.openflexo.fge.geom.FGEAbstractLine; import org.openflexo.fge.geom.FGEGeometricObject; import org.openflexo.fge.geom.FGEGeometricObject.SimplifiedCardinalDirection; import org.openflexo.fge.geom.FGELine; import org.openflexo.fge.geom.FGEPoint; import org.openflexo.fge.geom.FGERectangle; import org.openflexo.fge.geom.FGEShape; import org.openflexo.fge.graphics.BackgroundStyle; import org.openflexo.fge.graphics.FGEGraphics; public class FGESubstractionArea extends FGEOperationArea { private static final Logger logger = Logger.getLogger(FGESubstractionArea.class.getPackage().getName()); private FGEArea containerArea; private FGEArea substractedArea; private boolean isStrict; /** * Build a new FGESubstractionArea given container area and substracted area. Really build this operation area without trying to compute * or simplify it. * * @param containerArea * container area * @param substractedArea * area to substract to container area * @param isStrict * boolean indicating if a point located in the border of substracted area should be consider inside resulting area or not If * * <pre> * isStrict * </pre> * * is true, point should NOT be considered. */ public FGESubstractionArea(FGEArea containerArea, FGEArea substractedArea, boolean isStrict) { super(); this.containerArea = containerArea; this.substractedArea = substractedArea; this.isStrict = isStrict; } /** * Build a new Area given container area and substracted area. Try to compute, and simplify resulting area * * @param containerArea * container area * @param substractedArea * area to substract to container area * @param isStrict * boolean indicating if a point located in the border of substracted area should be consider inside resulting area or not If * * <pre> * isStrict * </pre> * * is true, point should NOT be considered. * @return */ public static FGEArea makeSubstraction(FGEArea containerArea, FGEArea substractedArea, boolean isStrict) { return makeSubstraction(containerArea, substractedArea, isStrict, true); } /** * Build a new Area given container area and substracted area. Try to compute, and simplify resulting area * * @param containerArea * container area * @param substractedArea * area to substract to container area * @param isStrict * boolean indicating if a point located in the border of substracted area should be consider inside resulting area or not If * * <pre> * isStrict * </pre> * * is true, point should NOT be considered. * @param checkNonNullIntersection * indicates if non-null intersection should be checked (take care about infinite loop !) * @return */ protected static FGEArea makeSubstraction(FGEArea containerArea, FGEArea substractedArea, boolean isStrict, boolean checkNonNullIntersection) { if (containerArea instanceof FGEEmptyArea) { return new FGEEmptyArea(); } if (substractedArea instanceof FGEEmptyArea) { return containerArea.clone(); } if (checkNonNullIntersection && containerArea.intersect(substractedArea) instanceof FGEEmptyArea) { return containerArea.clone(); } if (substractedArea.containsArea(containerArea)) { return new FGEEmptyArea(); } if (containerArea instanceof FGEUnionArea) { List<FGEArea> objects = new ArrayList<FGEArea>(); FGEUnionArea union = (FGEUnionArea) containerArea; for (FGEArea a : union.getObjects()) { if (substractedArea.containsArea(a)) { continue; } objects.add(makeSubstraction(a, substractedArea, isStrict)); } if (objects.size() == 0) { return new FGEEmptyArea(); } else if (objects.size() == 1) { return objects.get(0); } else { return FGEUnionArea.makeUnion(objects); } } return new FGESubstractionArea(containerArea, substractedArea, isStrict); } @Override public String toString() { return "FGESubstractionArea: " + containerArea + "-" + substractedArea; } @Override public boolean containsPoint(FGEPoint p) { return containerArea.containsPoint(p) && (!substractedArea.containsPoint(p) || !isStrict() && isPointLocatedOnSubstractedAreaBorder(p)); } @Override public boolean containsLine(FGEAbstractLine l) { return containsPoint(l.getP1()) && containsPoint(l.getP2()); } @Override public boolean containsArea(FGEArea a) { if (a instanceof FGEPoint) { return containsPoint((FGEPoint) a); } if (a instanceof FGELine) { return containsLine((FGELine) a); } if (a instanceof FGEShape) { return FGEShape.AreaComputation.isShapeContainedInArea((FGEShape) a, this); } return false; } protected boolean isPointLocatedOnSubstractedAreaBorder(FGEPoint testPoint) { if (!substractedArea.containsPoint(testPoint)) { return false; } if (substractedArea instanceof FGEShape) { return ((FGEShape) substractedArea).nearestOutlinePoint(testPoint).equals(testPoint); } else { // Little hack // Test with 4 points located juste near this point (at 2*EPSILON, which is the equals criteria) // If one of those point is not located inside substracted area, this means // that test point was "borderline" FGEPoint p1 = new FGEPoint(testPoint.x - 2 * FGEGeometricObject.EPSILON, testPoint.y); if (!substractedArea.containsPoint(p1)) { return true; } FGEPoint p2 = new FGEPoint(testPoint.x + 2 * FGEGeometricObject.EPSILON, testPoint.y); if (!substractedArea.containsPoint(p2)) { return true; } FGEPoint p3 = new FGEPoint(testPoint.x, testPoint.y - 2 * FGEGeometricObject.EPSILON); if (!substractedArea.containsPoint(p3)) { return true; } FGEPoint p4 = new FGEPoint(testPoint.x, testPoint.y + 2 * FGEGeometricObject.EPSILON); if (!substractedArea.containsPoint(p4)) { return true; } return false; } } @Override public FGESubstractionArea transform(AffineTransform t) { return new FGESubstractionArea(containerArea.transform(t), substractedArea.transform(t), isStrict); } @Override public void paint(FGEGraphics g) { // TODO // Use a finite method, using Java2D to perform shape computation // in the area defined by supplied FGEGraphics getContainerArea().paint(g); BackgroundStyle old = g.getDefaultBackground(); BackgroundStyle bs = BackgroundStyle.makeColoredBackground(java.awt.Color.GRAY); bs.setUseTransparency(true); g.setDefaultBackground(bs); getSubstractedArea().paint(g); g.setDefaultBackground(old); } @Override public FGEPoint getNearestPoint(FGEPoint aPoint) { if (containsPoint(aPoint)) { return aPoint.clone(); } FGEPoint returned = containerArea.getNearestPoint(aPoint); if (returned == null) { return null; } if (!substractedArea.containsPoint(returned)) { return returned; } // We have an other chance here ! if (substractedArea instanceof FGEShape && !isStrict()) { FGEPoint outlinePoint = ((FGEShape) substractedArea).nearestOutlinePoint(aPoint); if (containsPoint(outlinePoint)) { return outlinePoint; } } // TODO: we can implement a recursive method, trying to invoke getNearest() alternatively // on each objects, to obtain a possible result (not sure, but works on most cases) logger.warning("Not implemented yet !!!!"); return null; } /** * Return a flag indicating if this area is finite or not * * @return */ @Override public final boolean isFinite() { return containerArea.isFinite(); // TODO: doesn't handle the case where area2 makes this operation finite } /** * If this area is finite, return embedding bounds as a FGERectangle (this is not guaranteed to be optimal in some cases). For * non-finite areas (if this area is not finite), return null * * @return */ @Override public final FGERectangle getEmbeddingBounds() { // TODO: Sub-optimal, but sufficient for now return containerArea.getEmbeddingBounds(); } /** * 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) { if (containsPoint(from)) { return from.clone(); } FGEPoint returned = containerArea.nearestPointFrom(from, orientation); if (!substractedArea.containsPoint(returned)) { return returned; } // TODO: to implement logger.warning("Not implemented yet !!!!"); return null; } public FGEArea getContainerArea() { return containerArea; } public FGEArea getSubstractedArea() { return substractedArea; } @Override public boolean equals(Object obj) { if (obj instanceof FGESubstractionArea) { FGESubstractionArea sub = (FGESubstractionArea) obj; return sub.getContainerArea().equals(getContainerArea()) && sub.getSubstractedArea().equals(getSubstractedArea()) && sub.isStrict == isStrict; } return super.equals(obj); } /** * Return a boolean indicating if a point located in the border of substracted area should be consider inside resulting area or not. If * * <pre> * isStrict * </pre> * * is true, point should NOT be considered. * * @return */ public boolean isStrict() { return isStrict; } }