MonotoneChain.java :  » Physics » dyn4j » org » dyn4j » game2d » geometry » hull » Java Open Source

Java Open Source » Physics » dyn4j 
dyn4j » org » dyn4j » game2d » geometry » hull » MonotoneChain.java
/*
 * Copyright (c) 2011 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.game2d.geometry.hull;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Stack;

import org.dyn4j.game2d.geometry.Segment;
import org.dyn4j.game2d.geometry.Vector2;

/**
 * Implementation of the Andrew's Monotone Chain convex hull algorithm.
 * <p>
 * This implementation is not sensitive to colinear points and returns only
 * the points of the convex hull.
 * <p>
 * This algorithm is O(n log n) worst case where n is the number of points.
 * <p>
 * If the input point array has a size of 1 or 2 the input point array is returned.
 * @author William Bittle
 * @version 2.2.3
 * @since 2.2.0
 */
public class MonotoneChain implements HullGenerator {
  /**
   * Represents a comparator that sorts points by their x coordinate
   * lowest to highest then by the y coordinate.
   * @author William Bittle
   * @version 2.2.0
   * @since 2.2.0
   */
  private class PointComparator implements Comparator<Vector2> {
    /* (non-Javadoc)
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */
    @Override
    public int compare(Vector2 p1, Vector2 p2) {
      // first sort on the x coordinate
      int value = (int) Math.signum(p1.x - p2.x);
      // check for equal
      if (value == 0) {
        // if they are equal then sort on the y coordinate
        return (int) Math.signum(p1.y - p2.y);
      } else {
        return value;
      }
    }
  }
  
  /* (non-Javadoc)
   * @see org.dyn4j.game2d.geometry.hull.HullGenerator#generate(org.dyn4j.game2d.geometry.Vector2[])
   */
  @Override
  public Vector2[] generate(Vector2... points) {
    // check for a null array
    if (points == null) throw new NullPointerException("Cannot generate a convex hull from a null point array.");
    
    // get the size
    int size = points.length;
    // check the size
    if (size == 1 || size == 2) return points;
    
    try {
      // sort the points
      Arrays.sort(points, new PointComparator());
    } catch (NullPointerException e) {
      // if any comparison generates a null pointer exception
      // throw a null pointer exception with a good message
      throw new NullPointerException("The array of points cannot contain null points.");
    }
    
    // find the points whose x values are the smallest and largest
    int minmin = 0, minmax = 0, maxmin = 0, maxmax = 0;
    // minmin == minmax if there exists only one point who has the smallest
    // x coordinate, likewise, maxmin == maxmax if there exists only one point
    // who has the largest x coordinate
    for (int i = 1; i < size; i++) {
      // get the current values
      Vector2 minxminy = points[minmin];
      Vector2 minxmaxy = points[minmax];
      Vector2 maxxminy = points[maxmin];
      Vector2 maxxmaxy = points[maxmax];
      // get the current point
      Vector2 p = points[i];
      // check against the minimum x
      if (p.x < minxminy.x) {
        // its the new minimum
        minmin = i;
        minmax = i;
      } else if (p.x == minxminy.x) {
        // if they are equal then we need to 
        // check the y coordinate
        if (p.y > minxmaxy.y) {
          minmax = i;
        } else if (p.y < minxminy.y) {
          minmin = i;
        }
      }
      // check against the maximum x
      if (p.x > maxxminy.x) {
        // its the new maximum
        maxmin = i;
        maxmax = i;
      } else if (p.x == maxxminy.x) {
        // if they are equall then we need to 
        // check the y coordinate
        if (p.y > maxxmaxy.y) {
          maxmax = i;
        } else if (p.y < maxxminy.y) {
          maxmin = i;
        }
      } 
    }
    
    // build the lower convex hull
    Stack<Vector2> lower = new Stack<Vector2>();
    Vector2 lp1 = points[maxmin];
    Vector2 lp2 = points[minmin];
    lower.push(points[minmin]);
    // loop over the points between the min and max
    for (int i = minmax + 1; i <= maxmin; i++) {
      // get the current point
      Vector2 p = points[i];
      // where is it relative to the dividing line?
      if (Segment.getLocation(p, lp1, lp2) >= 0.0) {
        // if its on or to the left of the dividing line
        // check if this invalidates any points currently
        // in the convex hull
        while (lower.size() >= 2) {
          Vector2 p1 = lower.peek();
          Vector2 p2 = lower.get(lower.size() - 2);
          // check if the point is to the left of the
          // last edge in the current convex hull
          if (Segment.getLocation(p, p2, p1) > 0.0) {
            // if so, we can safely add the new point and
            // maintain convexity
            break;
          }
          // otherwise we need to remove the top point because
          // it creates a concavity
          lower.pop();
        }
        // when we are done always add the point
        // (it will be removed later if it creates a concavity)
        lower.push(p);
      }
    }
    
    // build the upper convex hull
    Stack<Vector2> upper = new Stack<Vector2>();
    Vector2 up1 = points[minmax];
    Vector2 up2 = points[maxmax];
    upper.push(points[maxmax]);
    // loop over the points between the min and max
    for (int i = maxmax - 1; i >= minmax; i--) {
      // get the current point
      Vector2 p = points[i];
      // where is it relative to the dividing line?
      if (Segment.getLocation(p, up1, up2) >= 0.0) {
        // if its on or to the left of the dividing line
        // check if this invalidates any points currently
        // in the convex hull
        while (upper.size() >= 2) {
          Vector2 p1 = upper.peek();
          Vector2 p2 = upper.get(upper.size() - 2);
          // check if the point is to the left of the
          // last edge in the current convex hull
          if (Segment.getLocation(p, p2, p1) > 0.0) {
            // if so, we can safely add the new point and
            // maintain convexity
            break;
          }
          // otherwise we need to remove the top point because
          // it creates a concavity
          upper.pop();
        }
        // when we are done always add the point
        // (it will be removed later if it creates a concavity)
        upper.push(p);
      }
    }
    
    // check if the first element of the upper hull is the same as
    // the last element of the lower hull
    if (upper.firstElement() == lower.lastElement()) {
      upper.remove(0);
    }
    // likewise check the first element of the lower hull with the
    // last element of the upper hull
    if (lower.firstElement() == upper.lastElement()) {
      lower.remove(0);
    }
    // append all the upper hull points to the lower hull
    // creating the complete convex hull
    lower.addAll(upper);
    
    // create and fill an array with the hull points
    Vector2[] hull = new Vector2[lower.size()];
    lower.toArray(hull);
    
    // return the hull
    return hull;
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.