/* * Convex hull algorithm - Library (Java) * * Copyright (c) 2017 Project Nayuki * https://www.nayuki.io/page/convex-hull-algorithm * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 program (see COPYING.txt and COPYING.LESSER.txt). * If not, see . */ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; public final class ConvexHull { // Returns a new list of points representing the convex hull of // the given set of points. The convex hull excludes collinear points. // This algorithm runs in O(n log n) time. public static List makeHull(List points) { List newPoints = new ArrayList<>(points); Collections.sort(newPoints); return makeHullPresorted(newPoints); } // Returns the convex hull, assuming that each points[i] <= points[i + 1]. Runs in O(n) time. public static List makeHullPresorted(List points) { if (points.size() <= 1) return new ArrayList<>(points); // Andrew's monotone chain algorithm. Positive y coordinates correspond to "up" // as per the mathematical convention, instead of "down" as per the computer // graphics convention. This doesn't affect the correctness of the result. List upperHull = new ArrayList<>(); for (Point p : points) { while (upperHull.size() >= 2) { Point q = upperHull.get(upperHull.size() - 1); Point r = upperHull.get(upperHull.size() - 2); if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.remove(upperHull.size() - 1); else break; } upperHull.add(p); } upperHull.remove(upperHull.size() - 1); List lowerHull = new ArrayList<>(); for (int i = points.size() - 1; i >= 0; i--) { Point p = points.get(i); while (lowerHull.size() >= 2) { Point q = lowerHull.get(lowerHull.size() - 1); Point r = lowerHull.get(lowerHull.size() - 2); if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.remove(lowerHull.size() - 1); else break; } lowerHull.add(p); } lowerHull.remove(lowerHull.size() - 1); if (!(upperHull.size() == 1 && upperHull.equals(lowerHull))) upperHull.addAll(lowerHull); return upperHull; } } final class Point implements Comparable { public final double x; public final double y; public Point(double x, double y) { this.x = x; this.y = y; } public String toString() { return String.format("Point(%g, %g)", x, y); } public boolean equals(Object obj) { if (!(obj instanceof Point)) return false; else { Point other = (Point)obj; return x == other.x && y == other.y; } } public int hashCode() { return Objects.hash(x, y); } public int compareTo(Point other) { if (x != other.x) return Double.compare(x, other.x); else return Double.compare(y, other.y); } }