/*
* (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.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import org.openflexo.fge.geom.FGEAbstractLine;
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.FGEGraphics;
public class FGEIntersectionArea extends FGEOperationArea {
private static final Logger logger = Logger.getLogger(FGEIntersectionArea.class.getPackage().getName());
private Vector<FGEArea> _objects;
public static FGEArea makeIntersection(FGEArea... objects) {
return makeIntersection(Arrays.asList(objects));
}
public static FGEArea makeIntersection(List<? extends FGEArea> objects) {
// 1. Amongst the complete list, are there any objects which are completely contained by another? If yes, then the containing-object
// is no longer necessary since, the contained object is equal to the intersection of the containing object and the contained
// object.
// For example, the intersection of a point and a line (considering that the point is on that line, it intersects that line), is the
// point itself.
List<FGEArea> areas = getDevelopedAreas(objects);
List<FGEArea> nonEmbeddedObjects = new ArrayList<FGEArea>();
for (FGEArea area : areas) {
boolean shouldAdd = true;
for (FGEArea a : nonEmbeddedObjects) {
if (area.containsArea(a)) {
shouldAdd = false;
}
}
if (shouldAdd) {
Vector<FGEArea> noMoreNecessaryObjects = new Vector<FGEArea>();
for (FGEArea a : nonEmbeddedObjects) {
if (a.containsArea(area)) {
noMoreNecessaryObjects.add(a);
}
}
for (FGEArea removeThat : noMoreNecessaryObjects) {
while (nonEmbeddedObjects.remove(removeThat)) {
;
}
}
nonEmbeddedObjects.add(area);
}
}
// Try to make intersection both/both with all objects
// > reduce intersection as much as possible
boolean tryToIntersectBothBoth = true;
while (tryToIntersectBothBoth) {
tryToIntersectBothBoth = false;
for (int i = 0; i < nonEmbeddedObjects.size(); i++) {
FGEArea a1 = nonEmbeddedObjects.get(i);
for (int j = i + 1; j < nonEmbeddedObjects.size(); j++) {
FGEArea a2 = nonEmbeddedObjects.get(j);
FGEArea intersect = a1.intersect(a2);
if (!(intersect instanceof FGEIntersectionArea)) {
if (intersect instanceof FGEEmptyArea) {
return new FGEEmptyArea();
}
nonEmbeddedObjects.remove(a1);
nonEmbeddedObjects.remove(a2);
nonEmbeddedObjects.add(intersect);
i = j = nonEmbeddedObjects.size();
tryToIntersectBothBoth = true;
}
}
}
}
if (nonEmbeddedObjects.size() == 0) {
return new FGEEmptyArea();
} else if (nonEmbeddedObjects.size() == 1) {
return nonEmbeddedObjects.get(0).clone();
} else {
FGEIntersectionArea returned = new FGEIntersectionArea(nonEmbeddedObjects);
if (returned.isDevelopable()) {
return returned.makeDevelopped();
}
return returned;
}
}
private static List<FGEArea> getDevelopedAreas(List<? extends FGEArea> objects) {
List<FGEArea> areas = new ArrayList<FGEArea>();
for (FGEArea area : objects) {
if (area instanceof FGEIntersectionArea) {
areas.addAll(getDevelopedAreas(((FGEIntersectionArea) area).getObjects()));
} else {
areas.add(area);
}
}
return areas;
}
public static void main(String[] args) {
FGELine line1 = new FGELine(new FGEPoint(0, 0), new FGEPoint(0, 1));// Vertical left line
FGELine line2 = new FGELine(new FGEPoint(0, 1), new FGEPoint(1, 1));// Horizontal bottom line
FGELine line3 = new FGELine(new FGEPoint(0, 0), new FGEPoint(1, 1));// Diagonal line (top-left to bottom-right)
FGELine line4 = new FGELine(new FGEPoint(0, 1), new FGEPoint(1, 0));// Diagonal line (top-right to bottom-left)
System.out.println("Intersection1: " + makeIntersection(line1, line2));
System.out.println("Intersection2: " + makeIntersection(line3, line2));
System.out.println("Intersection2: " + makeIntersection(line3, line4));
System.out.println("Intersection3: " + makeIntersection(line1, line2, line3));
System.out.println("Intersection4: " + makeIntersection(line1, line2, line4));
}
public FGEIntersectionArea() {
super();
_objects = new Vector<FGEArea>();
}
public FGEIntersectionArea(FGEArea... objects) {
this();
for (FGEArea o : objects) {
_objects.add(o.clone());
}
}
public FGEIntersectionArea(List<? extends FGEArea> objects) {
this();
for (FGEArea o : objects) {
_objects.add(o.clone());
}
}
public Vector<FGEArea> getObjects() {
return _objects;
}
public void setObjects(Vector<FGEArea> objects) {
if (_objects != null) {
_objects.clear();
} else {
_objects = new Vector<FGEArea>();
}
for (FGEArea o : objects) {
_objects.add(o.clone());
}
}
public void addToObjects(FGEArea obj) {
_objects.add(obj.clone());
}
public void removeFromObjects(FGEArea obj) {
_objects.remove(obj);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("FGEIntersectionArea: nObjects=" + _objects.size() + "\n");
for (int i = 0; i < _objects.size(); i++) {
sb.append(" " + (i + 1) + " > " + _objects.elementAt(i) + "\n");
}
return sb.toString();
}
@Override
public boolean containsPoint(FGEPoint p) {
if (_objects.size() == 0) {
return false;
}
for (FGEArea a : _objects) {
if (!a.containsPoint(p)) {
return false;
}
}
return true;
}
@Override
public boolean containsLine(FGEAbstractLine l) {
if (_objects.size() == 0) {
return false;
}
for (FGEArea a : _objects) {
if (!a.containsLine(l)) {
return false;
}
}
return true;
}
@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;
}
@Override
public FGEArea transform(AffineTransform t) {
FGEArea[] all = new FGEArea[_objects.size()];
for (int i = 0; i < _objects.size(); i++) {
all[i] = _objects.get(i).transform(t);
}
return new FGEIntersectionArea(all);
}
@Override
public void paint(FGEGraphics g) {
// TODO
// Use a finite method, using Java2D to perform shape computation
// in the area defined by supplied FGEGraphics
for (FGEArea a : getObjects()) {
a.paint(g);
}
}
@Override
public FGEPoint getNearestPoint(FGEPoint aPoint) {
if (containsPoint(aPoint)) {
return aPoint.clone();
}
if (getObjects().size() == 0) {
logger.warning("getNearestPoint() called for " + this + ": no objects !");
return null;
}
// Little heuristic to find nearest point
// (not working in all cases !)
Vector<FGEPoint> potentialPoints = new Vector<FGEPoint>();
for (int i = 0; i < getObjects().size(); i++) {
FGEPoint tryThis = _getApproximatedNearestPoint(aPoint, i);
if (tryThis != null) {
potentialPoints.add(tryThis);
}
}
if (potentialPoints.size() == 0) {
logger.warning("getNearestPoint() called for " + this
+ ": Not implemented yet (tried to compute heuristic, but failing to obtain a result)");
return null;
}
else {
double bestDistance = Double.POSITIVE_INFINITY;
FGEPoint bestPoint = null;
for (FGEPoint p : potentialPoints) {
double dist = FGEPoint.distance(p, aPoint);
if (dist < bestDistance) {
bestPoint = p;
bestDistance = dist;
}
}
return bestPoint;
}
}
/**
* Little heuristic to find nearest point for a formal intersection (not working in all cases !)
*
* @param aPoint
* @param firstTriedObjectIndex
* @return
*/
private FGEPoint _getApproximatedNearestPoint(FGEPoint aPoint, int firstTriedObjectIndex) {
int MAX_TRIES = 10;
int tries = 0;
FGEPoint returned = aPoint.clone();
// System.out.println("_getApproximatedNearestPoint() called for "+aPoint+" on "+this);
while (!containsPoint(returned) && tries < MAX_TRIES) {
FGEArea current = getObjects().elementAt(firstTriedObjectIndex);
firstTriedObjectIndex++;
if (firstTriedObjectIndex >= getObjects().size()) {
firstTriedObjectIndex = 0;
}
// System.out.println("> Try on "+current);
// System.out.println(" obtained "+current.getNearestPoint(returned)+" from "+returned);
returned = current.getNearestPoint(returned);
tries++;
}
if (containsPoint(returned)) {
// System.out.println("Found this: "+returned);
return returned;
}
return null;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FGEIntersectionArea) {
FGEIntersectionArea inters = (FGEIntersectionArea) obj;
if (getObjects().size() != inters.getObjects().size()) {
return false;
}
for (int i = 0; i < getObjects().size(); i++) {
FGEArea a = getObjects().get(i);
// Equals even if not same order
if (inters.getObjects().indexOf(a) == -1) {
return false;
}
}
return true;
}
return super.equals(obj);
}
/**
* Return a flag indicating if this area is finite or not An union area is finite if and only if at least on area is finite
*
* @return
*/
@Override
public final boolean isFinite() {
if (_objects.size() == 0) {
return true;
}
for (FGEArea a : _objects) {
if (a.isFinite()) {
return true;
}
}
return false;
}
/**
* 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 contains a least one non-finite area), return null
*
* @return
*/
@Override
public final FGERectangle getEmbeddingBounds() {
if (!isFinite()) {
return null;
}
FGERectangle returned = null;
for (FGEArea a : _objects) {
FGERectangle r = a.getEmbeddingBounds();
if (r != null) {
if (returned == null) {
returned = r;
} else {
FGEArea intersect = returned.intersect(r);
if (intersect instanceof FGERectangle) {
returned = (FGERectangle) intersect;
} else if (!(intersect instanceof FGEIntersectionArea) && intersect.isFinite()) {
returned = intersect.getEmbeddingBounds();
} else {
logger.warning("Cannot compute embedding bounds for " + this);
return null;
}
}
}
}
return returned;
}
public boolean isDevelopable() {
if (getObjects().size() <= 1) {
return false;
}
return _findFirstUnionArea(this) != null || _findFirstSubstractionArea(this) != null;
}
private static FGEUnionArea _findFirstUnionArea(FGEIntersectionArea intersection) {
for (FGEArea o : intersection.getObjects()) {
if (o instanceof FGEUnionArea) {
return (FGEUnionArea) o;
}
}
return null;
}
private static FGESubstractionArea _findFirstSubstractionArea(FGEIntersectionArea intersection) {
for (FGEArea o : intersection.getObjects()) {
if (o instanceof FGESubstractionArea) {
return (FGESubstractionArea) o;
}
}
return null;
}
public FGEArea makeDevelopped() {
if (!isDevelopable()) {
return clone();
}
FGEArea returned = developUnions();
if (returned instanceof FGEIntersectionArea) {
FGEIntersectionArea intersection = (FGEIntersectionArea) returned;
if (!intersection.isDevelopable()) {
return clone();
}
returned = developSubstractions();
}
return returned;
}
private FGEArea developUnions() {
if (_findFirstUnionArea(this) == null) {
return clone();
}
FGEUnionArea union = _findFirstUnionArea(this);
FGEArea area = null;
Vector<FGEArea> others = new Vector<FGEArea>();
for (FGEArea o : getObjects()) {
if (o != union) {
if (area == null) {
area = o;
} else {
others.add(o);
}
}
}
if (area == null) {
logger.warning("Inconsistent data while computing developUnions() in FGEIntersectionArea");
return clone();
} else {
// logger.info("develop "+this);
FGEArea developedArea = _developAsIntersection(union, area);
if (others.size() > 0) {
others.add(developedArea);
FGEIntersectionArea result = new FGEIntersectionArea(others);
if (result.isDevelopable()) {
return result.developUnions();
}
}
// logger.info("obtain "+developedArea);
return developedArea;
}
}
private FGEArea _developAsIntersection(FGEUnionArea union, FGEArea area) {
Vector<FGEArea> unionObjects = new Vector<FGEArea>();
for (FGEArea o : union.getObjects()) {
unionObjects.add(o.intersect(area));
}
return FGEUnionArea.makeUnion(unionObjects);
}
private FGEArea developSubstractions() {
if (_findFirstSubstractionArea(this) == null) {
return clone();
}
FGESubstractionArea sub = _findFirstSubstractionArea(this);
FGEArea area = null;
Vector<FGEArea> others = new Vector<FGEArea>();
for (FGEArea o : getObjects()) {
if (o != sub) {
if (area == null) {
area = o;
} else {
others.add(o);
}
}
}
if (area == null) {
logger.warning("Inconsistent data while computing developSubstractions() in FGEIntersectionArea");
return clone();
} else {
// logger.info("develop "+this);
FGEArea developedArea = _developAsSubstraction(sub, area);
if (others.size() > 0) {
others.add(developedArea);
FGEIntersectionArea result = new FGEIntersectionArea(others);
if (result.isDevelopable()) {
return result.developSubstractions();
}
}
// logger.info("obtain "+developedArea);
return developedArea;
}
}
private FGEArea _developAsSubstraction(FGESubstractionArea sub, FGEArea area) {
return FGESubstractionArea.makeSubstraction(sub.getContainerArea().intersect(area), sub.getSubstractedArea().intersect(area),
sub.isStrict(), false);
}
/**
* 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);
FGEArea intersect = intersect(hl);
if (intersect instanceof FGEIntersectionArea) {
// Avoid infinite loop
logger.warning("Cannot find nearest from " + from + " from " + orientation);
return null;
}
return intersect.nearestPointFrom(from, orientation);
}
}