/*
* Copyright (c) 2010, Frederik Vanhoutte This library is free software; you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
* http://creativecommons.org/licenses/LGPL/2.1/ This library 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 Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
* Fifth Floor, Boston, MA 02110-1301 USA
*/
package wblut.geom;
import java.util.List;
import wblut.WB_Epsilon;
import javolution.util.FastList;
// TODO: Auto-generated Javadoc
/**
* Planar polygon class.
*/
public class WB_Polygon2D {
/** Ordered array of WB_Point. */
public WB_Point2d[] points;
/** Number of points. */
public int n;
/**
* Instantiates a new WB_Polygon.
*/
public WB_Polygon2D() {
points = new WB_Point2d[0];
n = 0;
}
/**
* Instantiates a new WB_Polygon.
*
* @param points array of WB_Point, no copies are made
* @param n number of points
*/
public WB_Polygon2D(final WB_Point2d[] points, final int n) {
this.points = points;
this.n = n;
}
/**
* Instantiates a new WB_Polygon.
*
* @param points array of WB_Point
* @param n number of points
* @param copy copy points?
*/
public WB_Polygon2D(final WB_Point2d[] points, final int n,
final boolean copy) {
if (copy == false) {
this.points = points;
} else {
this.points = new WB_Point2d[n];
for (int i = 0; i < n; i++) {
this.points[i] = points[i].get();
}
}
this.n = n;
}
/**
* Instantiates a new WB_Polygon2D.
*
* @param points arrayList of WB_XY
*/
public WB_Polygon2D(final List<WB_Point2d> points) {
n = points.size();
this.points = new WB_Point2d[n];
for (int i = 0; i < n; i++) {
this.points[i] = points.get(i);
}
}
/**
* Set polygon.
*
* @param points array of WB_Point, no copies are made
* @param n number of points
*/
public void set(final WB_Point2d[] points, final int n) {
this.points = points;
this.n = n;
}
/**
* Set polygon.
*
* @param poly source polygon, no copies are made
*/
public void set(final WB_Polygon2D poly) {
points = poly.points;
n = poly.n;
}
/**
* Set polygon.
*
* @param points arrayList of WB_Point, no copies are made
* @param n number of points
*/
public void set(final List<WB_Point2d> points, final int n) {
this.points = new WB_Point2d[n];
for (int i = 0; i < n; i++) {
this.points[i] = points.get(i);
}
this.n = n;
}
/**
* Get deep copy.
*
* @return copy
*/
public WB_Polygon2D get() {
final WB_Point2d[] newPoints = new WB_Point2d[n];
for (int i = 0; i < n; i++) {
newPoints[i] = points[i].get();
}
return new WB_Polygon2D(newPoints, n);
}
/**
* Get shallow copy.
*
* @return copy
*/
public WB_Polygon2D getNoCopy() {
return new WB_Polygon2D(points, n);
}
/**
* Closest point on polygon to given point.
*
* @param p point
* @return closest point of polygon
*/
public WB_Point2d closestPoint(final WB_Point2d p) {
double d = Double.POSITIVE_INFINITY;
int id = -1;
for (int i = 0; i < n; i++) {
final double cd = WB_Distance2D.sqDistance(p, points[i]);
if (cd < d) {
id = i;
d = cd;
}
}
return points[id];
}
/**
* Index of closest point on polygon to given point.
*
* @param p point
* @return index of closest point of polygon
*/
public int closestIndex(final WB_Point2d p) {
double d = Double.POSITIVE_INFINITY;
int id = -1;
for (int i = 0; i < n; i++) {
final double cd = WB_Distance2D.sqDistance(p, points[i]);
if (cd < d) {
id = i;
d = cd;
}
}
return id;
}
/**
* Checks if point at index is convex.
*
* @param i index
* @return WB.VertexType.FLAT,WB.VertexType.CONVEX,WB.VertexType.CONCAVE
*/
public WB_VertexType2D isConvex(final int i) {
final WB_Point2d vp = points[(i == 0) ? n - 1 : i - 1]
.subAndCopy(points[i]);
vp.normalize();
final WB_Point2d vn = points[(i == n - 1) ? 0 : i + 1]
.subAndCopy(points[i]);
vn.normalize();
final double cross = vp.x * vn.y - vp.y * vn.x;
if (WB_Epsilon.isZero(cross)) {
return WB_VertexType2D.FLAT;
} else if (Math.acos(vp.dot(vn)) < Math.PI) {
return WB_VertexType2D.CONVEX;
} else {
return WB_VertexType2D.CONCAVE;
}
}
/**
* Triangulate polygon.
*
* @return arrayList of WB_Triangle, points are not copied
*/
public List<WB_ExplicitTriangle2D> triangulate() {
final WB_Triangulation2D tri = new WB_Triangulation2D();
tri.startWithBoundary(points);
return tri.getExplicitTrianglesAsList();
}
/**
* Triangulate polygon.
*
* @return arrayList of WB_IndexedTriangle, points are not copied
*/
public List<WB_IndexedTriangle2D> indexedTriangulate() {
final WB_Triangulation2D tri = new WB_Triangulation2D();
tri.startWithBoundary(points, true);
return tri.getIndexedTrianglesAsList(points, true);
}
/**
* Removes point.
*
* @param i index of point to remove
* @return new WB_Polygon with point removed
*/
public WB_Polygon2D removePoint(final int i) {
final WB_Point2d[] newPoints = new WB_Point2d[n - 1];
for (int j = 0; j < i; j++) {
newPoints[j] = points[j];
}
for (int j = i; j < n - 1; j++) {
newPoints[j] = points[j + 1];
}
return new WB_Polygon2D(newPoints, n - 1);
}
/**
* Remove flat points.
*
* @return new WB_Polygon with superfluous points removed
*/
public WB_Polygon2D removeFlatPoints() {
return removeFlatPoints(0);
}
/**
* Removes the flat points.
*
* @param start the start
* @return the w b_ polygon2 d
*/
private WB_Polygon2D removeFlatPoints(final int start) {
for (int i = start; i < n; i++) {
if (isConvex(i) == WB_VertexType2D.FLAT) {
return removePoint(i).removeFlatPoints(i);
}
}
return this;
}
/**
* Adds point.
*
* @param i index to put point
* @param p point
* @return new WB_Polygon with point added
*/
public WB_Polygon2D addPoint(final int i, final WB_Point2d p) {
final WB_Point2d[] newPoints = new WB_Point2d[n + 1];
for (int j = 0; j < i; j++) {
newPoints[j] = points[j];
}
newPoints[i] = p;
for (int j = i + 1; j < n + 1; j++) {
newPoints[j] = points[j - 1];
}
return new WB_Polygon2D(newPoints, n + 1);
}
/**
* Refine polygon and smooth with simple Laplacian filter.
*
* @return new refined WB_Polygon
*/
public WB_Polygon2D smooth() {
final WB_Point2d[] newPoints = new WB_Point2d[2 * n];
for (int i = 0, j = n - 1; i < n; j = i, i++) {
newPoints[2 * i] = points[j].addAndCopy(points[i]);
newPoints[2 * i].mult(0.5);
newPoints[2 * i + 1] = points[i].get();
}
final WB_Point2d[] sPoints = new WB_Point2d[2 * n];
for (int i = 0, j = 2 * n - 1; i < 2 * n; j = i, i++) {
int k = i + 1;
if (k == 2 * n) {
k = 0;
}
sPoints[i] = newPoints[j].addAndCopy(newPoints[k]);
sPoints[i].mult(0.5);
}
return new WB_Polygon2D(sPoints, 2 * n);
}
/**
* Trim convex polygon.
*
* @param poly the poly
* @param d the d
*/
public static void trimConvexPolygon(final WB_Polygon2D poly, final double d) {
final WB_Polygon2D cpoly = poly.get();
final int n = cpoly.n; // get number of vertices
// iterate over n-1 edges
final WB_Polygon2D frontPoly = new WB_Polygon2D();// needed by
// splitPolygon
// to store one half
final WB_Polygon2D backPoly = new WB_Polygon2D();// needed by
// splitPolygon
// to store other half
WB_Point2d p1, p2, origin;
WB_Point2d v, normal;
for (int i = 0, j = n - 1; i < n; j = i, i++) {
p1 = cpoly.points[i];// startpoint of edge
p2 = cpoly.points[j];// endpoint of edge
// vector along edge
v = p2.subAndCopy(p1);
v.normalize();
// edge normal is perpendicular to edge and plane normal
normal = new WB_Point2d(v.y, -v.x);
// center of edge
origin = p1.addAndCopy(p2).mult(0.5);
// offset cutting plane origin by the desired distance d
origin.add(d * normal.x, d * normal.y);
splitPolygonInto(poly, new WB_Line2D(origin, v), frontPoly,
backPoly);
poly.set(frontPoly);
}
}
/**
* Trim convex polygon.
*
* @param d the d
*/
public void trimConvexPolygon(final double d) {
trimConvexPolygon(this, d);
}
/**
* Trim convex polygon.
*
* @param poly the poly
* @param d the d
*/
public static void trimConvexPolygon(final WB_Polygon2D poly,
final double[] d) {
// iterate over n-1 edges
final WB_Polygon2D frontPoly = new WB_Polygon2D();// needed by
// splitPolygon
// to store one half
final WB_Polygon2D backPoly = new WB_Polygon2D();// needed by
// splitPolygon
// to store other half
WB_Point2d p1, p2, origin;
WB_Point2d v, normal;
for (int i = 0, j = poly.n - 1; i < poly.n; j = i, i++) {
p1 = poly.points[i];// startpoint of edge
p2 = poly.points[j];// endpoint of edge
// vector along edge
v = p2.subAndCopy(p1);
v.normalize();
// edge normal is perpendicular to edge and plane normal
normal = new WB_Point2d(v.y, -v.x);
// center of edge
origin = p1.addAndCopy(p2).mult(0.5);
// offset cutting plane origin by the desired distance d
origin.add(d[i] * normal.x, d[i] * normal.y);
splitPolygonInto(poly, new WB_Line2D(origin, v), frontPoly,
backPoly);
poly.set(frontPoly);
}
}
/**
* Trim convex polygon.
*
* @param d the d
*/
public void trimConvexPolygon(final double[] d) {
trimConvexPolygon(this, d);
}
/**
* Split polygon into.
*
* @param poly the poly
* @param L split line
* @param frontPoly front subpoly
* @param backPoly back subpoly
*/
public static void splitPolygonInto(final WB_Polygon2D poly,
final WB_Line2D L, final WB_Polygon2D frontPoly,
final WB_Polygon2D backPoly) {
int numFront = 0;
int numBack = 0;
final FastList<WB_Point2d> frontVerts = new FastList<WB_Point2d>(20);
final FastList<WB_Point2d> backVerts = new FastList<WB_Point2d>(20);
final int numVerts = poly.n;
if (numVerts > 0) {
WB_Point2d a = poly.points[numVerts - 1];
WB_ClassifyPointToLine2D aSide = L.classifyPointToLine2D(a);
WB_Point2d b;
WB_ClassifyPointToLine2D bSide;
for (int n = 0; n < numVerts; n++) {
WB_IntersectionResult i = new WB_IntersectionResult();
b = poly.points[n];
bSide = L.classifyPointToLine2D(b);
if (bSide == WB_ClassifyPointToLine2D.POINT_IN_FRONT_OF_LINE) {
if (aSide == WB_ClassifyPointToLine2D.POINT_BEHIND_LINE) {
i = WB_Intersection2D.closestPoint2D(L,
new WB_ExplicitSegment2D(a, b));
frontVerts.add((WB_Point2d) i.object);
numFront++;
backVerts.add((WB_Point2d) i.object);
numBack++;
}
frontVerts.add(b);
numFront++;
} else if (bSide == WB_ClassifyPointToLine2D.POINT_BEHIND_LINE) {
if (aSide == WB_ClassifyPointToLine2D.POINT_IN_FRONT_OF_LINE) {
i = WB_Intersection2D.closestPoint2D(L,
new WB_ExplicitSegment2D(a, b));
/*
* if (classifyPointToPlane(i.p1, P) !=
* ClassifyPointToPlane.POINT_ON_PLANE) { System.out
* .println("Inconsistency: intersection not on plane");
* }
*/
frontVerts.add((WB_Point2d) i.object);
numFront++;
backVerts.add((WB_Point2d) i.object);
numBack++;
} else if (aSide == WB_ClassifyPointToLine2D.POINT_ON_LINE) {
backVerts.add(a);
numBack++;
}
backVerts.add(b);
numBack++;
} else {
frontVerts.add(b);
numFront++;
if (aSide == WB_ClassifyPointToLine2D.POINT_BEHIND_LINE) {
backVerts.add(b);
numBack++;
}
}
a = b;
aSide = bSide;
}
frontPoly.set(frontVerts, numFront);
backPoly.set(backVerts, numBack);
}
}
/**
* Split polygon into.
*
* @param L the l
* @param frontPoly the front poly
* @param backPoly the back poly
*/
public void splitPolygonInto(final WB_Line2D L,
final WB_Polygon2D frontPoly, final WB_Polygon2D backPoly) {
splitPolygonInto(get(), L, frontPoly, backPoly);
}
/**
* To segments.
*
* @return the list
*/
public List<WB_IndexedSegment2D> toSegments() {
final List<WB_IndexedSegment2D> segments = new FastList<WB_IndexedSegment2D>(
n);
for (int i = 0, j = n - 1; i < n; j = i, i++) {
segments.add(new WB_IndexedSegment2D(j, i, points));
}
return segments;
}
/**
* To explicit segments.
*
* @return the list
*/
public List<WB_ExplicitSegment2D> toExplicitSegments() {
final List<WB_ExplicitSegment2D> segments = new FastList<WB_ExplicitSegment2D>(
n);
for (int i = 0, j = n - 1; i < n; j = i, i++) {
segments.add(new WB_ExplicitSegment2D(points[j], points[i]));
}
return segments;
}
/**
* Negate.
*
* @return the w b_ polygon2 d
*/
public WB_Polygon2D negate() {
final WB_Point2d[] negPoints = new WB_Point2d[n];
for (int i = 0; i < n; i++) {
negPoints[i] = points[n - 1 - i];
}
return new WB_Polygon2D(negPoints, n);
}
/**
* Negate.
*
* @param polys the polys
* @return the list
*/
public static List<WB_Polygon2D> negate(final List<WB_Polygon2D> polys) {
final List<WB_Polygon2D> neg = new FastList<WB_Polygon2D>();
for (int i = 0; i < polys.size(); i++) {
neg.add(polys.get(i).negate());
}
return neg;
}
/**
* Intersection seg.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_ExplicitSegment2D> intersectionSeg(
final WB_Polygon2D P, final WB_Polygon2D Q) {
final FastList<WB_ExplicitSegment2D> pos = new FastList<WB_ExplicitSegment2D>();
final FastList<WB_ExplicitSegment2D> neg = new FastList<WB_ExplicitSegment2D>();
final FastList<WB_ExplicitSegment2D> coSame = new FastList<WB_ExplicitSegment2D>();
final FastList<WB_ExplicitSegment2D> coDiff = new FastList<WB_ExplicitSegment2D>();
final FastList<WB_ExplicitSegment2D> intersect = new FastList<WB_ExplicitSegment2D>();
final WB_BSPTree2D tree = new WB_BSPTree2D();
tree.build(P);
for (int i = 0, j = Q.n - 1; i < Q.n; j = i, i++) {
pos.clear();
neg.clear();
coSame.clear();
coDiff.clear();
final WB_ExplicitSegment2D S = new WB_ExplicitSegment2D(
Q.points[j], Q.points[i]);
tree.partitionSegment(S, pos, neg, coSame, coDiff);
intersect.addAll(pos);
intersect.addAll(coSame);
}
tree.build(Q);
for (int i = 0, j = P.n - 1; i < P.n; j = i, i++) {
pos.clear();
neg.clear();
coSame.clear();
coDiff.clear();
final WB_ExplicitSegment2D S = new WB_ExplicitSegment2D(
P.points[j], P.points[i]);
tree.partitionSegment(S, pos, neg, coSame, coDiff);
intersect.addAll(pos);
intersect.addAll(coSame);
}
return intersect;
}
/**
* Intersection.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_Polygon2D> intersection(final WB_Polygon2D P,
final WB_Polygon2D Q) {
return extractPolygons(intersectionSeg(P, Q));
}
/**
* Union seg.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_ExplicitSegment2D> unionSeg(final WB_Polygon2D P,
final WB_Polygon2D Q) {
final WB_Polygon2D nP = P.negate();
final WB_Polygon2D nQ = Q.negate();
return WB_ExplicitSegment2D.negate(intersectionSeg(nP, nQ));
}
/**
* Union.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_Polygon2D> union(final WB_Polygon2D P,
final WB_Polygon2D Q) {
return extractPolygons(unionSeg(P, Q));
}
/**
* Subtract seg.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_ExplicitSegment2D> subtractSeg(final WB_Polygon2D P,
final WB_Polygon2D Q) {
final WB_Polygon2D nQ = Q.negate();
return intersectionSeg(P, nQ);
}
/**
* Subtract.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_Polygon2D> subtract(final WB_Polygon2D P,
final WB_Polygon2D Q) {
return extractPolygons(subtractSeg(P, Q));
}
/**
* Exclusive or.
*
* @param P the p
* @param Q the q
* @return the list
*/
public static List<WB_Polygon2D> exclusiveOr(final WB_Polygon2D P,
final WB_Polygon2D Q) {
final List<WB_Polygon2D> tmp = subtract(P, Q);
tmp.addAll(subtract(Q, P));
return tmp;
}
/**
* Extract polygons.
*
* @param segs the segs
* @return the list
*/
public static List<WB_Polygon2D> extractPolygons(
final List<WB_ExplicitSegment2D> segs) {
final List<WB_Polygon2D> result = new FastList<WB_Polygon2D>();
final List<WB_ExplicitSegment2D> leftovers = new FastList<WB_ExplicitSegment2D>();
final List<WB_ExplicitSegment2D> cleanedsegs = clean(segs);
leftovers.addAll(cleanedsegs);
while (leftovers.size() > 0) {
final FastList<WB_ExplicitSegment2D> currentPolygon = new FastList<WB_ExplicitSegment2D>();
final boolean loopFound = tryToFindLoop(leftovers, currentPolygon);
if (loopFound) {
final FastList<WB_Point2d> points = new FastList<WB_Point2d>();
for (int i = 0; i < currentPolygon.size(); i++) {
points.add(currentPolygon.get(i).getOrigin());
}
if (points.size() > 2) {
WB_Polygon2D poly = new WB_Polygon2D(points);
poly = poly.removeFlatPoints();
result.add(poly);
}
}
leftovers.removeAll(currentPolygon);
}
return result;
}
/**
* Clean.
*
* @param segs the segs
* @return the list
*/
private static List<WB_ExplicitSegment2D> clean(
final List<WB_ExplicitSegment2D> segs) {
final List<WB_ExplicitSegment2D> cleanedsegs = new FastList<WB_ExplicitSegment2D>();
final WB_KDTree2Dold<Integer> tree = new WB_KDTree2Dold<Integer>();
int i = 0;
for (i = 0; i < segs.size(); i++) {
if (!WB_Epsilon.isZeroSq(WB_Distance2D.sqDistance(segs.get(i)
.getOrigin(), segs.get(i).getEnd()))) {
tree.put(segs.get(i).getOrigin(), 2 * i);
tree.put(segs.get(i).getEnd(), 2 * i + 1);
cleanedsegs.add(new WB_ExplicitSegment2D(segs.get(i)
.getOrigin(), segs.get(i).getEnd()));
break;
}
}
for (; i < segs.size(); i++) {
if (!WB_Epsilon.isZeroSq(WB_Distance2D.sqDistance(segs.get(i)
.getOrigin(), segs.get(i).getEnd()))) {
WB_Point2d origin = segs.get(i).getOrigin();
WB_Point2d end = segs.get(i).getEnd();
WB_KDNeighbor2D<Integer>[] nn = tree.getNearestNeighbors(
origin, 1);
if (WB_Epsilon.isZeroSq(nn[0].sqDistance())) {
origin = nn[0].point();
} else {
tree.put(segs.get(i).getOrigin(), 2 * i);
}
nn = tree.getNearestNeighbors(end, 1);
if (WB_Epsilon.isZeroSq(nn[0].sqDistance())) {
end = nn[0].point();
} else {
tree.put(segs.get(i).getEnd(), 2 * i + 1);
}
cleanedsegs.add(new WB_ExplicitSegment2D(origin, end));
}
}
return cleanedsegs;
}
/**
* Try to find loop.
*
* @param segs the segs
* @param loop the loop
* @return true, if successful
*/
private static boolean tryToFindLoop(final List<WB_ExplicitSegment2D> segs,
final List<WB_ExplicitSegment2D> loop) {
final List<WB_ExplicitSegment2D> localSegs = new FastList<WB_ExplicitSegment2D>();
localSegs.addAll(segs);
WB_Segment2D start = localSegs.get(0);
loop.add(localSegs.get(0));
boolean found = false;
do {
found = false;
for (int i = 0; i < localSegs.size(); i++) {
if (WB_Epsilon.isZeroSq(WB_Distance2D.sqDistance(
localSegs.get(i).getOrigin(), start.getEnd()))) {
// if (localSegs.get(i).origin() == start.end()) {
start = localSegs.get(i);
loop.add(localSegs.get(i));
found = true;
break;
}
}
if (found) {
localSegs.remove(start);
}
} while ((start != segs.get(0)) && found);
if ((loop.size() > 0) && (start == segs.get(0))) {
return true;
}
return false;
}
/**
* To polygon.
*
* @return the w b_ explicit polygon
*/
public WB_ExplicitPolygon toPolygon() {
final WB_Point3d[] points3D = new WB_Point3d[n];
for (int i = 0; i < n; i++) {
points3D[i] = new WB_Point3d(points[i].x, points[i].y, 0);
}
return new WB_ExplicitPolygon(points3D, n);
}
}