All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dyn4j.geometry.decompose.Bayazit Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
/*
 * Copyright (c) 2010-2016 William Bittle  http://www.dyn4j.org/
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this list of conditions 
 *     and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
 *     and the following disclaimer in the documentation and/or other materials provided with the 
 *     distribution.
 *   * Neither the name of dyn4j nor the names of its contributors may be used to endorse or 
 *     promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.dyn4j.geometry.decompose;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.dyn4j.Epsilon;
import org.dyn4j.geometry.Convex;
import org.dyn4j.geometry.Geometry;
import org.dyn4j.geometry.Segment;
import org.dyn4j.geometry.Vector2;
import org.dyn4j.resources.Messages;

/**
 * Implementation of the Bayazit convex decomposition algorithm for simple polygons.
 * 

* This algorithm is a O(nr) complexity algorithm where n is the number of input vertices and r is the number of * output convex polygons. This algorithm can achieve optimal decompositions, however this is not guaranteed. * @author William Bittle * @version 3.1.10 * @since 2.2.0 * @see Bayazit */ public class Bayazit implements Decomposer { /* (non-Javadoc) * @see org.dyn4j.geometry.decompose.Decomposer#decompose(org.dyn4j.geometry.Vector2[]) */ @Override public List decompose(Vector2... points) { // check for null array if (points == null) throw new NullPointerException(Messages.getString("geometry.decompose.nullArray")); // get the number of points int size = points.length; // check the size if (size < 4) throw new IllegalArgumentException(Messages.getString("geometry.decompose.invalidSize")); // get the winding order double winding = Geometry.getWinding(points); // reverse the array if the points are in clockwise order if (winding < 0.0) { Geometry.reverseWinding(points); } // create a list for the points to go in List polygon = new ArrayList(); // copy the points to the list Collections.addAll(polygon, points); // create a list for the polygons to live List polygons = new ArrayList(); // decompose the polygon this.decomposePolygon(polygon, polygons); // return the result return polygons; } /** * Internal recursive method to decompose the given polygon into convex sub-polygons. * @param polygon the polygon to decompose * @param polygons the list to store the convex polygons resulting from the decomposition */ protected void decomposePolygon(List polygon, List polygons) { // get the size of the given polygon int size = polygon.size(); // initialize Vector2 upperIntersection = new Vector2(); Vector2 lowerIntersection = new Vector2(); double upperDistance = Double.MAX_VALUE; double lowerDistance = Double.MAX_VALUE; double closestDistance = Double.MAX_VALUE; int upperIndex = 0; int lowerIndex = 0; int closestIndex = 0; List lower = new ArrayList(); List upper = new ArrayList(); // loop over all the vertices for (int i = 0; i < size; i++) { // get the current vertex Vector2 p = polygon.get(i); // get the adjacent vertices Vector2 p0 = polygon.get(i - 1 < 0 ? size - 1 : i - 1); Vector2 p1 = polygon.get(i + 1 == size ? 0 : i + 1); // check if the vertex is a reflex vertex if (isReflex(p0, p, p1)) { // loop over the vertices to determine if both extended // adjacent edges intersect one edge (in which case a // steiner point will be added) for (int j = 0; j < size; j++) { Vector2 q = polygon.get(j); // get the adjacent vertices Vector2 q0 = polygon.get(j - 1 < 0 ? size - 1 : j - 1); Vector2 q1 = polygon.get(j + 1 == size ? 0 : j + 1); // create a storage location for the intersection point Vector2 s = new Vector2(); // extend the previous edge // does the line p0->p go between the vertices q and q0 if (left(p0, p, q) && rightOn(p0, p, q0)) { // get the intersection point if (this.getIntersection(p0, p, q, q0, s)) { // make sure the intersection point is to the right of // the edge p1->p (this makes sure its inside the polygon) if (right(p1, p, s)) { // get the distance from p to the intersection point s double dist = p.distanceSquared(s); // only save the smallest if (dist < lowerDistance) { lowerDistance = dist; lowerIntersection.set(s); lowerIndex = j; } } } } // extend the next edge // does the line p1->p go between q and q1 if (left(p1, p, q1) && rightOn(p1, p, q)) { // get the intersection point if (this.getIntersection(p1, p, q, q1, s)) { // make sure the intersection point is to the left of // the edge p0->p (this makes sure its inside the polygon) if (left(p0, p, s)) { // get the distance from p to the intersection point s double dist = p.distanceSquared(s); // only save the smallest if (dist < upperDistance) { upperDistance = dist; upperIntersection.set(s); upperIndex = j; } } } } } // if the lower index and upper index are equal then this means // that the range of p only included an edge (both extended previous // and next edges of p only intersected the same edge, therefore no // point exists within that range to connect to) if (lowerIndex == (upperIndex + 1) % size) { // create a steiner point in the middle Vector2 s = upperIntersection.sum(lowerIntersection).multiply(0.5); // partition the polygon if (i < upperIndex) { lower.addAll(polygon.subList(i, upperIndex + 1)); lower.add(s); upper.add(s); if (lowerIndex != 0) upper.addAll(polygon.subList(lowerIndex, size)); upper.addAll(polygon.subList(0, i + 1)); } else { if (i != 0) lower.addAll(polygon.subList(i, size)); lower.addAll(polygon.subList(0, upperIndex + 1)); lower.add(s); upper.add(s); upper.addAll(polygon.subList(lowerIndex, i + 1)); } } else { // otherwise we need to find the closest "visible" point to p if (lowerIndex > upperIndex) { upperIndex += size; } closestIndex = lowerIndex; // find the closest visible point for (int j = lowerIndex; j <= upperIndex; j++) { int jmod = j % size; Vector2 q = polygon.get(jmod); if (q == p || q == p0 || q == p1) continue; // check the distance first, since this is generally // a much faster operation than checking if its visible double dist = p.distanceSquared(q); if (dist < closestDistance) { if (this.isVisible(polygon, i, jmod)) { closestDistance = dist; closestIndex = jmod; } } } // once we find the closest partition the polygon if (i < closestIndex) { lower.addAll(polygon.subList(i, closestIndex + 1)); if (closestIndex != 0) upper.addAll(polygon.subList(closestIndex, size)); upper.addAll(polygon.subList(0, i + 1)); } else { if (i != 0) lower.addAll(polygon.subList(i, size)); lower.addAll(polygon.subList(0, closestIndex + 1)); upper.addAll(polygon.subList(closestIndex, i + 1)); } } // decompose the smaller first if (lower.size() < upper.size()) { decomposePolygon(lower, polygons); decomposePolygon(upper, polygons); } else { decomposePolygon(upper, polygons); decomposePolygon(lower, polygons); } // if the given polygon contains a reflex vertex, then return return; } } // if we get here, we know the given polygon has 0 reflex vertices // and is therefore convex, add it to the list of convex polygons if (polygon.size() < 3) { throw new IllegalArgumentException(Messages.getString("geometry.decompose.crossingEdges")); } Vector2[] vertices = new Vector2[polygon.size()]; polygon.toArray(vertices); polygons.add(Geometry.createPolygon(vertices)); } /** * Returns true if the given vertex, b, is a reflex vertex. *

* A reflex vertex is a vertex who's interior angle is greater * than 180 degrees. * @param p0 the vertex to test * @param p the previous vertex * @param p1 the next vertex * @return boolean */ protected boolean isReflex(Vector2 p0, Vector2 p, Vector2 p1) { // if the point p is to the right of the line p0-p1 then // the point is a reflex vertex return right(p1, p0, p); } /** * Returns true if the given point p is to the left * of the line created by a-b. * @param a the first point of the line * @param b the second point of the line * @param p the point to test * @return boolean */ protected boolean left(Vector2 a, Vector2 b, Vector2 p) { return Segment.getLocation(p, a, b) > 0; } /** * Returns true if the given point p is to the left * or on the line created by a-b. * @param a the first point of the line * @param b the second point of the line * @param p the point to test * @return boolean */ protected boolean leftOn(Vector2 a, Vector2 b, Vector2 p) { return Segment.getLocation(p, a, b) >= 0; } /** * Returns true if the given point p is to the right * of the line created by a-b. * @param a the first point of the line * @param b the second point of the line * @param p the point to test * @return boolean */ protected boolean right(Vector2 a, Vector2 b, Vector2 p) { return Segment.getLocation(p, a, b) < 0; } /** * Returns true if the given point p is to the right * or on the line created by a-b. * @param a the first point of the line * @param b the second point of the line * @param p the point to test * @return boolean */ protected boolean rightOn(Vector2 a, Vector2 b, Vector2 p) { return Segment.getLocation(p, a, b) <= 0; } /** * Returns true if the given lines intersect and returns the intersection point in * the p parameter. * @param a1 the first point of the first line * @param a2 the second point of the first line * @param b1 the first point of the second line * @param b2 the second point of the second line * @param p the destination object for the intersection point * @return boolean */ protected boolean getIntersection(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, Vector2 p) { // any point on a line can be found by the parametric equation: // P = (1 - t)A + tB // or // P.x = (1 - t)A.x + tB.x // P.y = (1 - t)A.y + tB.y // so we get: // P.x = (1 - t1)A1.x + t1A2.x // P.y = (1 - t1)A1.y + t1A2.y // P.x = (1 - t2)B1.x + t2B2.x // P.y = (1 - t2)B1.y + t2B2.y // since P is the same we can set the equations equal // (1 - t1)A1.x + t1A2.x = (1 - t2)B1.x + t2B2.x // (1 - t1)A1.y + t1A2.y = (1 - t2)B1.y + t2B2.y // A1.x - t1A1.x + t1A2.x = B1.x - t2B1.x + t2B2.x // A1.y - t1A1.y + t1A2.y = B1.y - t2B1.y + t2B2.y // t2(B1.x - B2.x) - t1(A1.x - A2.x) = B1.x - A1.x // t2(B1.y - B2.y) - t1(A1.y - A2.y) = B1.y - A1.y // solve the system of equations // t1 = -(B1.x - A1.x - t2(B1.x - B2.x)) / (A1.x - A2.x) // t2(B2.y - B2.y) + (B1.x - A1.x - t2(B1.x - B2.x)) / (A1.x - A2.x) * (A1.y - A2.y) = B1.y - A1.y // t2(B2.y - B2.y)(A1.x - A2.x) + (B1.x - A1.x - t2(B1.x - B2.x))(A1.y - A2.y) = (B1.y - A1.y)(A1.x - A2.x) // t2S2.yS1.x + B1.xS1.y - A1.xS1.y - t2S2.xS1.y = B1.yS1.x - A1.yS1.x // t2(S2.yS1.x - S2.xS1.y) = B1.yS1.x - A1.yS1.x - B1.xS1.y + A1.xS1.y // t2(S1.cross(S2)) = B1.yS1.x - B1.xS1.y + A1.xS1.y - A1.yS1.x // t2(S1.cross(S2)) = A1.cross(S1) - B1.cross(S1) // t2 = (A1.cross(S1) - B1.cross(S1)) / S1.cross(S2) // if S1.cross(S2) is near zero then there is no solution // compute S1 and S2 Vector2 s1 = a1.difference(a2); Vector2 s2 = b1.difference(b2); // compute the cross product (the determinant if we used matrix solving techniques) double det = s1.cross(s2); // make sure the matrix isn't singular (the lines could be parallel) if (Math.abs(det) <= Epsilon.E) { // return false since there is no way that the segments could be intersecting return false; } else { // pre-divide the determinant det = 1.0 / det; // compute t2 double t2 = det * (a1.cross(s1) - b1.cross(s1)); // compute the intersection point // P = B1(1.0 - t2) + B2(t2) p.x = b1.x * (1.0 - t2) + b2.x * t2; p.y = b1.y * (1.0 - t2) + b2.y * t2; // return that they intersect return true; } } /** * Returns true if the vertex at index i can see the vertex at index j. * @param polygon the current polygon * @param i the ith vertex * @param j the jth vertex * @return boolean * @since 3.1.10 */ private boolean isVisible(List polygon, int i, int j) { int s = polygon.size(); Vector2 iv0, iv, iv1; Vector2 jv0, jv, jv1; iv0 = polygon.get(i == 0 ? s - 1 : i - 1); iv = polygon.get(i); iv1 = polygon.get(i + 1 == s ? 0 : i + 1); jv0 = polygon.get(j == 0 ? s - 1 : j - 1); jv = polygon.get(j); jv1 = polygon.get(j + 1 == s ? 0 : j + 1); // can i see j if (this.isReflex(iv0, iv, iv1)) { if (leftOn(iv, iv0, jv) && rightOn(iv, iv1, jv)) return false; } else { if (rightOn(iv, iv1, jv) || leftOn(iv, iv0, jv)) return false; } // can j see i if (this.isReflex(jv0, jv, jv1)) { if (leftOn(jv, jv0, iv) && rightOn(jv, jv1, iv)) return false; } else { if (rightOn(jv, jv1, iv) || leftOn(jv, jv0, iv)) return false; } // make sure the segment from i to j doesn't intersect any edges for (int k = 0; k < s; k++) { int ki1 = k + 1 == s ? 0 : k + 1; if (k == i || k == j || ki1 == i || ki1 == j) continue; Vector2 k1 = polygon.get(k); Vector2 k2 = polygon.get(ki1); Vector2 in = Segment.getSegmentIntersection(iv, jv, k1, k2); if (in != null) return false; } return true; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy