/*******************************************************************************
* Copyright (c) 2011, 2015 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - contribution for Bugzilla #355997
*
*******************************************************************************/
package org.eclipse.gef.geometry.planar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.eclipse.gef.geometry.euclidean.Angle;
/**
* A combination of {@link Rectangle}s. The {@link Rectangle}s that build up a
* {@link Region} do not have to be touching. The area covered by the
* {@link Region} is exactly the area that all of its corresponding
* {@link Rectangle}s are covering.
*
* A {@link Region} differentiates between the internal {@link Rectangle}s and
* the external {@link Rectangle}s. The external {@link Rectangle}s are those
* that you feed it, in order to construct the {@link Region}. The internal
* {@link Rectangle}s are those used for computations of the {@link Region}.
* They are defined to not share any area, so that only their borders can be
* overlapping.
*
* @author anyssen
* @author mwienand
*
*/
public class Region extends AbstractMultiShape
implements ITranslatable<Region>, IScalable<Region>, IRotatable<Ring> {
/**
* Cuts the given {@link Rectangle}s along the given parallel to the x-axis.
*
* @param y
* the distance of the cut-line to the x-axis
* @param parts
* the {@link Rectangle}s to cut along that line
*/
private static void cutH(double y, ArrayList<Rectangle> parts) {
for (Rectangle r : new ArrayList<>(parts)) {
if (r.y < y && y < r.y + r.height) {
parts.remove(r);
parts.add(new Rectangle(r.x, r.y, r.width, y - r.y));
parts.add(new Rectangle(r.x, y, r.width, r.y + r.height - y));
}
}
}
/**
* Cuts the given {@link Rectangle}s along the given parallel to the y-axis.
*
* @param x
* the distance of the cut-line to the y-axis
* @param parts
* the {@link Rectangle}s to cut along that line
*/
private static void cutV(double x, ArrayList<Rectangle> parts) {
for (Rectangle r : new ArrayList<>(parts)) {
if (r.x < x && x < r.x + r.width) {
parts.remove(r);
parts.add(new Rectangle(r.x, r.y, x - r.x, r.height));
parts.add(new Rectangle(x, r.y, r.x + r.width - x, r.height));
}
}
}
private static final long serialVersionUID = 1L;
private ArrayList<Rectangle> rects;
/**
* Constructs a new {@link Region} not covering any area.
*/
public Region() {
rects = new ArrayList<>();
}
/**
* Constructs a new {@link Region} from the given list of {@link Rectangle}
* s.
*
* The given {@link Rectangle}s are {@link #add(Rectangle)}ed to the
* {@link Region} one after the other.
*
* @param rectangles
* The array of {@link Rectangle}s from which this {@link Region}
* is constructed.
*/
public Region(Rectangle... rectangles) {
this();
rects.add(rectangles[0].getCopy());
for (int i = 1; i < rectangles.length; i++) {
add(rectangles[i].getCopy());
}
}
/**
* Constructs a new {@link Region} from the given other {@link Region}. In
* other words, it copies the given other {@link Region}.
*
* @param other
* The {@link Region} from which this {@link Region} is
* constructed.
*/
public Region(Region other) {
rects = new ArrayList<>(other.rects.size());
for (Rectangle or : other.rects) {
rects.add(or.getCopy());
}
}
/**
* Adds the given {@link Rectangle} to this {@link Region}.
*
* To assure the required conditions for internal {@link Rectangle}s, the
* given {@link Rectangle} is cut into several sub-{@link Rectangle}s so
* that no internal {@link Rectangle}s share any area.
*
* @param rectangle
* the {@link Rectangle} to add to this {@link Region}
* @return <code>this</code> for convenience
*/
public Region add(Rectangle rectangle) {
ArrayList<Rectangle> toAdd = new ArrayList<>(1);
toAdd.add(rectangle.getCopy());
for (Rectangle retain : rects) {
for (Rectangle addend : new ArrayList<>(toAdd)) {
ArrayList<Rectangle> parts = new ArrayList<>(8);
parts.add(addend);
if (addend.x <= retain.x && retain.x <= addend.x + addend.width
|| retain.x <= addend.x
&& addend.x <= retain.x + retain.width) {
cutH(retain.y, parts);
cutH(retain.y + retain.height, parts);
}
if (addend.y <= retain.y && retain.y <= addend.y + addend.height
|| retain.y <= addend.y
&& addend.y <= retain.y + retain.height) {
cutV(retain.x, parts);
cutV(retain.x + retain.width, parts);
}
// filter inner parts:
for (Iterator<Rectangle> p = parts.iterator(); p.hasNext();) {
if (retain.contains(p.next())) {
p.remove();
}
}
toAdd.remove(addend);
toAdd.addAll(parts);
}
}
rects.addAll(toAdd);
return this;
}
@Override
public boolean contains(IGeometry g) {
return ShapeUtils.contains(this, g);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Region) {
Region o = (Region) obj;
// TODO: Invent a better algorithm.
return contains(o) && o.contains(this);
}
return false;
}
/**
* Collects all outline segments of the internal {@link Rectangle}s.
*
* @return all the outline segments of the internal {@link Rectangle}s
*/
@Override
protected Line[] getAllEdges() {
Stack<Line> edges = new Stack<>();
for (Rectangle r : rects) {
for (Line e : r.getOutlineSegments()) {
edges.push(e);
}
}
return edges.toArray(new Line[] {});
}
@Override
public Rectangle getBounds() {
if (rects.size() == 0) {
return null;
}
Rectangle bounds = rects.get(0).getBounds();
for (int i = 1; i < rects.size(); i++) {
bounds.union(rects.get(i).getBounds());
}
return bounds;
}
@Override
public Region getCopy() {
return new Region(this);
}
/**
* Computes the {@link Point}s of intersection of this {@link Region} with
* the given {@link ICurve}.
*
* @param c
* The {@link ICurve} for which outline intersections are
* computed.
* @return the intersection {@link Point}s
*/
public Point[] getOutlineIntersections(ICurve c) {
Set<Point> intersections = new HashSet<>(0);
for (Line seg : getOutlineSegments()) {
intersections.addAll(Arrays.asList(seg.getIntersections(c)));
}
return intersections.toArray(new Point[] {});
}
@Override
public Ring getRotatedCCW(Angle angle) {
Point centroid = getBounds().getCenter();
return getRotatedCCW(angle, centroid.x, centroid.y);
}
@Override
public Ring getRotatedCCW(Angle angle, double cx, double cy) {
Polygon[] polys = new Polygon[rects.size()];
for (int i = 0; i < polys.length; i++) {
polys[i] = rects.get(i).getRotatedCCW(angle, cx, cy);
}
return new Ring(polys);
}
@Override
public Ring getRotatedCCW(Angle angle, Point center) {
return getRotatedCCW(angle, center.x, center.y);
}
@Override
public Ring getRotatedCW(Angle angle) {
Point centroid = getBounds().getCenter();
return getRotatedCW(angle, centroid.x, centroid.y);
}
@Override
public Ring getRotatedCW(Angle angle, double cx, double cy) {
Polygon[] polys = new Polygon[rects.size()];
for (int i = 0; i < polys.length; i++) {
polys[i] = rects.get(i).getRotatedCW(angle, cx, cy);
}
return new Ring(polys);
}
@Override
public Ring getRotatedCW(Angle angle, Point center) {
return getRotatedCW(angle, center.x, center.y);
}
@Override
public Region getScaled(double factor) {
return getCopy().scale(factor);
}
@Override
public Region getScaled(double fx, double fy) {
return getCopy().scale(fx, fy);
}
@Override
public Region getScaled(double factor, double cx, double cy) {
return getCopy().scale(factor, cx, cy);
}
@Override
public Region getScaled(double fx, double fy, double cx, double cy) {
return getCopy().scale(fx, fy, cx, cy);
}
@Override
public Region getScaled(double fx, double fy, Point center) {
return getCopy().scale(fx, fy, center);
}
@Override
public Region getScaled(double factor, Point center) {
return getCopy().scale(factor, center);
}
@Override
public Rectangle[] getShapes() {
return rects.toArray(new Rectangle[] {});
}
@Override
public Ring getTransformed(AffineTransform t) {
List<Polygon> transformedRectangles = new ArrayList<>();
for (Rectangle r : rects) {
transformedRectangles.add(r.getTransformed(t));
}
return new Ring(transformedRectangles.toArray(new Polygon[] {}));
}
@Override
public Region getTranslated(double dx, double dy) {
return getCopy().translate(dx, dy);
}
@Override
public Region getTranslated(Point d) {
return getCopy().translate(d.x, d.y);
}
@Override
public Region scale(double factor) {
return scale(factor, factor);
}
@Override
public Region scale(double fx, double fy) {
Point centroid = getBounds().getCenter();
return scale(fx, fy, centroid.x, centroid.y);
}
@Override
public Region scale(double factor, double cx, double cy) {
return scale(factor, factor, cx, cy);
}
@Override
public Region scale(double fx, double fy, double cx, double cy) {
for (Rectangle r : rects) {
r.scale(fx, fy, cx, cy);
}
return this;
}
@Override
public Region scale(double fx, double fy, Point center) {
return scale(fx, fy, center.x, center.y);
}
@Override
public Region scale(double factor, Point center) {
return scale(factor, factor, center.x, center.y);
}
/**
* Constructs a new {@link Ring} that covers the same area as this
* {@link Region}.
*
* @return a new {@link Ring} that covers the same area as this
* {@link Region}
*/
public Ring toRing() {
Polygon[] polys = new Polygon[rects.size()];
Iterator<Rectangle> i = rects.iterator();
for (int j = 0; j < rects.size(); j++) {
polys[j] = i.next().toPolygon();
}
return new Ring(polys);
}
@Override
public Region translate(double dx, double dy) {
for (Rectangle r : rects) {
r.translate(dx, dy);
}
return this;
}
@Override
public Region translate(Point d) {
return translate(d.x, d.y);
}
}