/******************************************************************************* * 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.Iterator; import java.util.List; import java.util.Stack; import org.eclipse.gef.geometry.euclidean.Angle; import org.eclipse.gef.geometry.euclidean.Straight; import org.eclipse.gef.geometry.euclidean.Vector; /** * A combination of Polygons.... * * @author anyssen * @author mwienand * */ public class Ring extends AbstractMultiShape implements ITranslatable<Ring>, IScalable<Ring>, IRotatable<Ring> { /** * Triangulates the given triangle ({@link Polygon}) at the given * {@link Line}. The triangulation is done using the simpler * {@link #triangulate(Polygon, Point, Point)} method. The real and * imaginary {@link Point}s of intersection of the {@link Line} and the * {@link Polygon} are used as the split {@link Point}s. * * The triangulation is only done, if the {@link Line} intersects the * {@link Polygon}, i.e. at least one {@link Point} of the {@link Line} lies * inside the {@link Polygon} but not on its outline. * * @param p * the triangle ({@link Polygon}) to triangulate * @param l * the line that determines the split {@link Point}s * @return at least one and up to three {@link Polygon}s which are the * resulting triangles */ private static Polygon[] triangulate(Polygon p, Line l) { if (p == null) { throw new IllegalArgumentException( "The given Polygon parameter may not be null."); } if (l == null) { throw new IllegalArgumentException( "The given Line parameter may not be null."); } boolean intersecting = l.getIntersections(p.getOutline()).length == 2; if (!intersecting) { // test if at least one point of the line is really inside the // polygon for (Point lp : l.getPoints()) { if (p.contains(lp) && !p.getOutline().contains(lp)) { intersecting = true; break; } } if (!intersecting) { return new Polygon[] { p.getCopy() }; } } // calculate real and imaginary intersection points Straight s = new Straight(l); Point inters[] = new Point[2]; int i = 0; for (Line e : p.getOutlineSegments()) { Vector vi = s.getIntersection(new Straight(e)); if (vi != null) { Point poi = vi.toPoint(); if (e.contains(poi)) { if (i > 0 && inters[0].equals(poi)) { continue; } else { inters[i++] = poi; } } } if (i > 1) { break; } } if (inters[0] == null || inters[1] == null) { throw new IllegalStateException( "The determined points of intersection do not lie on the polygon. Call: triangulate(" + p + ", " + l + ")"); } return triangulate(p, inters[0], inters[1]); } /** * <p> * Splits a triangle at the line through points p1 and p2, which are * required to lie on the outline of the triangle. * </p> * * <p> * If the points p1 and p2 lie on the same edge on the triangle, a copy of * the given {@link Polygon} is returned. * </p> * * <p> * If one of the points lies on an edge, and the other point lies on a * vertex of the triangle, two {@link Polygon}s are returned. They represent * the areas left and right to the line through p1 and p2. * </p> * * <p> * If both points lie on different edges, three {@link Polygon}s are * returned. One of them represents the triangle which lies on one side of * the line through p1 and p2. The other two triangles are the triangulation * of the tetragon on the other side of the line through p1 and p2. * </p> * * @param p * @param p1 * @param p2 * @return */ private static Polygon[] triangulate(Polygon p, Point p1, Point p2) { Point[] v = p == null ? new Point[] {} : p.getPoints(); if (v.length != 3) { throw new IllegalArgumentException( "Only triangles are allowed as the Polygon parameter."); } if (p1 == null) { throw new IllegalArgumentException( "The given p1 Point parameter may not be null."); } if (p2 == null) { throw new IllegalArgumentException( "The given p2 Point parameter may not be null."); } Line[] e = new Line[] { new Line(v[0], v[1]), new Line(v[1], v[2]), new Line(v[2], v[0]) }; // determine the edges on which the points lie boolean p1_on_e0 = e[0].contains(p1); boolean p1_on_e1 = e[1].contains(p1); boolean p1_on_e2 = e[2].contains(p1); boolean p2_on_e0 = e[0].contains(p2); boolean p2_on_e1 = e[1].contains(p2); boolean p2_on_e2 = e[2].contains(p2); // if both points lie on the same edge, we have nothing to do if (p1_on_e0 && p2_on_e0 || p1_on_e1 && p2_on_e1 || p1_on_e2 && p2_on_e2) { return new Polygon[] { p.getCopy() }; } // check if both points are on the triangle else if (!(p1_on_e0 || p1_on_e1 || p1_on_e2) || !(p2_on_e0 || p2_on_e1 || p2_on_e2)) { throw new IllegalArgumentException( "The Point objects have to lie on the outline of the Polygon object."); } // determine if one of the points lies on a vertex else if (p1.equals(v[0])) { return new Polygon[] { new Polygon(v[0], v[1], p2), new Polygon(v[0], v[2], p2) }; } else if (p1.equals(v[1])) { return new Polygon[] { new Polygon(v[0], v[1], p2), new Polygon(v[1], v[2], p2) }; } else if (p1.equals(v[2])) { return new Polygon[] { new Polygon(v[0], v[2], p2), new Polygon(v[1], v[2], p2) }; } else if (p2.equals(v[0])) { return new Polygon[] { new Polygon(v[0], v[1], p1), new Polygon(v[0], v[2], p1) }; } else if (p2.equals(v[1])) { return new Polygon[] { new Polygon(v[0], v[1], p1), new Polygon(v[1], v[2], p1) }; } else if (p2.equals(v[2])) { return new Polygon[] { new Polygon(v[0], v[2], p1), new Polygon(v[1], v[2], p1) }; } // both points on different edges, determine isolated vertex else if (p1_on_e0 && p2_on_e2 || p1_on_e2 && p2_on_e0) { // v0 isolated return new Polygon[] { new Polygon(v[0], p1, p2), new Polygon(p1, p2, v[1]), new Polygon(p1_on_e0 ? p2 : p1, v[1], v[2]) }; } else if (p1_on_e0 && p2_on_e1 || p1_on_e1 && p2_on_e0) { // v1 isolated return new Polygon[] { new Polygon(v[1], p1, p2), new Polygon(p1, p2, v[0]), new Polygon(p1_on_e0 ? p2 : p1, v[0], v[2]) }; } else if (p1_on_e1 && p2_on_e2 || p1_on_e2 && p2_on_e1) { // v2 isolated return new Polygon[] { new Polygon(v[2], p1, p2), new Polygon(p1, p2, v[1]), new Polygon(p1_on_e1 ? p2 : p1, v[1], v[0]) }; } else { throw new IllegalStateException( "Unreachable, because for two points on a triangle, they have to be located either (edge, edge), (vertex, edge), or (edge, vertex)."); } } private static final long serialVersionUID = 1L; private ArrayList<Polygon> triangles; /** * Constructs a new empty {@link Ring}. */ public Ring() { triangles = new ArrayList<>(); } /** * Constructs a new {@link Ring} from the given {@link Polygon}s. * * @param polygons * The array of {@link Polygon}s from which this {@link Ring} is * constructed. */ public Ring(Polygon... polygons) { this(); for (Polygon p : polygons) { add(p); } } /** * Constructs a new {@link Ring} from the given other {@link Ring}. The * internal {@link IShape}s of the other {@link Ring} are copied to prevent * actions at a distance. * * @param other * The {@link Ring} from which this {@link Ring} is constructed. */ public Ring(Ring other) { this(); for (Polygon p : other.triangles) { add(p); } } /** * Adds the given {@link Polygon} to this {@link Ring}. * * @param p * The {@link Polygon} which is added to this {@link Ring}. * @return <code>this</code> for convenience */ public Ring add(Polygon p) { Stack<Polygon> toAdd = new Stack<>(); for (Polygon triangleToAdd : p.getTriangulation()) { // do not add "empty" triangles if (triangleToAdd.getArea() != 0) { toAdd.push(triangleToAdd); } } while (!toAdd.empty()) { Polygon triangleToAdd = toAdd.pop(); Stack<Polygon> localAddends = new Stack<>(); localAddends.push(triangleToAdd); for (Polygon triangleAlreadyThere : triangles) { for (Line e : triangleAlreadyThere.getOutlineSegments()) { Stack<Polygon> nextAddends = new Stack<>(); for (Iterator<Polygon> i = localAddends.iterator(); i .hasNext();) { Polygon addend = i.next(); i.remove(); for (Polygon subTriangleToAdd : triangulate(addend, e)) { if (!triangleAlreadyThere .contains(subTriangleToAdd)) { nextAddends.push(subTriangleToAdd); } } } localAddends = nextAddends; } } for (Polygon addend : localAddends) { triangles.add(addend); } } optimizeTriangles(); return this; } @Override public boolean contains(IGeometry g) { return ShapeUtils.contains(this, g); } @Override public boolean equals(Object obj) { // TODO: Invent a better algorithm. if (obj instanceof Ring) { Ring o = (Ring) obj; return contains(o) && o.contains(this); } return false; } private boolean findSharedAndOuterVertices(Polygon t1, Polygon t2, Point[] shared, Point[] outer) { Point[] t1Points = t1.getPoints(); Point[] t2Points = t2.getPoints(); boolean[] t1IsShared = new boolean[] { false, false, false }; boolean[] t2IsShared = new boolean[] { false, false, false }; int sc = 0; for (int i = 0; i < t1Points.length; i++) { for (int j = 0; j < t2Points.length; j++) { if (t1Points[i].equals(t2Points[j])) { if (sc++ == 2) { return false; } t1IsShared[i] = true; t2IsShared[j] = true; } } } if (sc != 2) { return false; } for (int i = 0, c = 0; i < t1Points.length; i++) { if (t1IsShared[i]) { shared[c++] = t1Points[i]; } else { outer[0] = t1Points[i]; } if (!t2IsShared[i]) { outer[1] = t2Points[i]; } } return true; } @Override protected Line[] getAllEdges() { Stack<Line> edges = new Stack<>(); for (Polygon t : triangles) { for (Line e : t.getOutlineSegments()) { edges.push(e); } } return edges.toArray(new Line[] {}); } @Override public Rectangle getBounds() { if (triangles.size() == 0) { return null; } Rectangle bounds = triangles.get(0).getBounds(); for (int i = 1; i < triangles.size(); i++) { bounds.union(triangles.get(i).getBounds()); } return bounds; } @Override public Ring getCopy() { return new Ring(this); } @Override public Ring getRotatedCCW(Angle angle) { return getCopy().rotateCCW(angle); } @Override public Ring getRotatedCCW(Angle angle, double cx, double cy) { return getCopy().rotateCCW(angle, cx, cy); } @Override public Ring getRotatedCCW(Angle angle, Point center) { return getCopy().rotateCCW(angle, center); } @Override public Ring getRotatedCW(Angle angle) { return getCopy().rotateCW(angle); } @Override public Ring getRotatedCW(Angle angle, double cx, double cy) { return getCopy().rotateCW(angle, cx, cy); } @Override public Ring getRotatedCW(Angle angle, Point center) { return getCopy().rotateCW(angle, center); } @Override public Ring getScaled(double factor) { return getCopy().scale(factor); } @Override public Ring getScaled(double fx, double fy) { return getCopy().scale(fx, fy); } @Override public Ring getScaled(double factor, double cx, double cy) { return getCopy().scale(factor, cx, cy); } @Override public Ring getScaled(double fx, double fy, double cx, double cy) { return getCopy().scale(fx, fy, cx, cy); } @Override public Ring getScaled(double fx, double fy, Point center) { return getCopy().scale(fx, fy, center); } @Override public Ring getScaled(double factor, Point center) { return getCopy().scale(factor, center); } @Override public Polygon[] getShapes() { return triangles.toArray(new Polygon[] {}); } @Override public Ring getTransformed(AffineTransform t) { List<Polygon> transformedTriangles = new ArrayList<>(); for (Polygon p : triangles) { transformedTriangles.add(p.getTransformed(t)); } return new Ring(transformedTriangles.toArray(new Polygon[] {})); } @Override public Ring getTranslated(double dx, double dy) { return getCopy().translate(dx, dy); } @Override public Ring getTranslated(Point d) { return getCopy().translate(d.x, d.y); } private Polygon mergeTriangles(Polygon t1, Polygon t2) { Point[] shared = new Point[2], outer = new Point[2]; boolean found = findSharedAndOuterVertices(t1, t2, shared, outer); if (found) { Line outerLink = new Line(outer[0], outer[1]); if (outerLink.contains(shared[0])) { return new Polygon(outer[0], outer[1], shared[1]); } else if (outerLink.contains(shared[1])) { return new Polygon(outer[0], outer[1], shared[0]); } } return null; } private void optimizeTriangles() { for (int i = 0; i < triangles.size(); i++) { Polygon t1 = triangles.get(i); for (int j = i + 1; j < triangles.size(); j++) { Polygon t2 = triangles.get(j); Polygon merge = mergeTriangles(t1, t2); if (merge != null) { triangles.set(i, merge); t1 = merge; triangles.remove(j); j = i; } } } } /** * Directly rotates this {@link Ring} counter-clock-wise around its center * {@link Point} by the given {@link Angle}. Direct adaptation means, that * <code>this</code> {@link PolyBezier} is modified in-place. * * @param angle * rotation {@link Angle} * @return <code>this</code> for convenience */ public Ring rotateCCW(Angle angle) { Point centroid = getBounds().getCenter(); return rotateCCW(angle, centroid.x, centroid.y); } /** * Directly rotates this {@link Ring} counter-clock-wise around the given * point (specified by cx and cy) by the given {@link Angle}. Direct * adaptation means, that <code>this</code> {@link PolyBezier} is modified * in-place. * * @param angle * rotation {@link Angle} * @param cx * x-coordinate of the {@link Point} to rotate around * @param cy * y-coordinate of the {@link Point} to rotate around * @return <code>this</code> for convenience */ public Ring rotateCCW(Angle angle, double cx, double cy) { for (Polygon p : triangles) { p.rotateCCW(angle, cx, cy); } return this; } /** * Directly rotates this {@link Ring} counter-clock-wise around the given * {@link Point} by the given {@link Angle}. Direct adaptation means, that * <code>this</code> {@link PolyBezier} is modified in-place. * * @param angle * rotation {@link Angle} * @param center * {@link Point} to rotate around * @return <code>this</code> for convenience */ public Ring rotateCCW(Angle angle, Point center) { return rotateCCW(angle, center.x, center.y); } /** * Directly rotates this {@link Ring} clock-wise around its center * {@link Point} by the given {@link Angle}. Direct adaptation means, that * <code>this</code> {@link PolyBezier} is modified in-place. * * @param angle * rotation {@link Angle} * @return <code>this</code> for convenience */ public Ring rotateCW(Angle angle) { Point centroid = getBounds().getCenter(); return rotateCW(angle, centroid.x, centroid.y); } /** * Directly rotates this {@link Ring} clock-wise around the given point * (specified by cx and cy) by the given {@link Angle}. Direct adaptation * means, that <code>this</code> {@link PolyBezier} is modified in-place. * * @param angle * rotation {@link Angle} * @param cx * x-coordinate of the {@link Point} to rotate around * @param cy * y-coordinate of the {@link Point} to rotate around * @return <code>this</code> for convenience */ public Ring rotateCW(Angle angle, double cx, double cy) { for (Polygon p : triangles) { p.rotateCW(angle, cx, cy); } return this; } /** * Directly rotates this {@link Ring} clock-wise around the given * {@link Point} by the given {@link Angle}. Direct adaptation means, that * <code>this</code> {@link PolyBezier} is modified in-place. * * @param angle * rotation {@link Angle} * @param center * {@link Point} to rotate around * @return <code>this</code> for convenience */ public Ring rotateCW(Angle angle, Point center) { return rotateCW(angle, center.x, center.y); } @Override public Ring scale(double factor) { return scale(factor, factor); } @Override public Ring scale(double fx, double fy) { Point center = getBounds().getCenter(); return scale(fx, fy, center.x, center.y); } @Override public Ring scale(double factor, double cx, double cy) { return scale(factor, factor, cx, cy); } @Override public Ring scale(double fx, double fy, double cx, double cy) { for (Polygon p : triangles) { p.scale(fx, fy, cx, cy); } return this; } @Override public Ring scale(double fx, double fy, Point center) { return scale(fx, fy, center.x, center.y); } @Override public Ring scale(double factor, Point center) { return scale(factor, factor, center.x, center.y); } @Override public Ring translate(double dx, double dy) { for (Polygon p : triangles) { p.translate(dx, dy); } return this; } @Override public Ring translate(Point d) { return translate(d.x, d.y); } }