/*******************************************************************************
* Copyright (c) 2016 Weasis Team 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:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.ui.model.utils.algo;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.weasis.core.api.gui.util.MathUtil;
public class ConvexHull {
public static final int COUNTERCLOCKWISE = 1;
public static final int CLOCKWISE = -1;
private List<Point2D.Double> pts;
public ConvexHull(List<Point2D.Double> pts) {
this.pts = removeDuplicates(pts);
}
public static List<Point2D.Double> removeDuplicates(List<Point2D.Double> points) {
TreeSet<Point2D.Double> treeSet = new TreeSet<>((p1, p2) -> {
if (p1.y < p2.y) {
return -1;
}
if (p1.y > p2.y) {
return +1;
}
if (p1.x < p2.x) {
return -1;
}
if (p1.x > p2.x) {
return +1;
}
return 0;
});
treeSet.addAll(points);
return new ArrayList<>(treeSet);
}
public List<Point2D.Double> getConvexHull() {
if (pts.size() < 3) {
return pts;
}
return grahamScan(preSort(pts)).stream().collect(Collectors.toList());
}
private static List<Double> preSort(List<Point2D.Double> pts) {
Point2D.Double p = pts.get(0);
for (int i = 1; i < pts.size(); i++) {
Point2D.Double pc = pts.get(i);
if ((pc.y < p.y) || (MathUtil.isEqual(pc.y, p.y) && (pc.x < p.x))) {
p = pc;
Collections.swap(pts, 0, i);
}
}
Collections.sort(pts, new RadialSorter(p));
return pts;
}
/**
* compute the convex hull with the Graham Scan algorithm
*
* @param pts
* a list of points
*
* @return a Deque containing the ordered points of the convex hull ring
*/
private static Deque<Point2D.Double> grahamScan(List<Point2D.Double> pts) {
ArrayDeque<Point2D.Double> ps = new ArrayDeque<>();
ps.addFirst(pts.get(2));
ps.addFirst(pts.get(1));
ps.addFirst(pts.get(0));
Point2D.Double p;
for (int i = 3; i < pts.size(); i++) {
p = ps.removeLast();
Point2D.Double pc = pts.get(i);
while (!ps.isEmpty() && getOrientation(ps.peekLast(), p, pc) > 0) {
p = ps.removeLast();
}
ps.addLast(p);
ps.addLast(pc);
}
ps.addLast(pts.get(0));
return ps;
}
private static int signum(double x) {
if (x > 0) {
return 1;
}
if (x < 0) {
return -1;
}
return 0;
}
/**
* Returns the index of the direction of the point c relative to a vector a-b.
*
* @param a
* the origin point of the vector
* @param b
* the final point of the vector
* @param c
* the point to compute the direction to
*
* @return 1 if c is counter-clockwise, (left) from a-b
* @return -1 if c is clockwise, (right) from a-b
* @return 0 if c is collinear with a-b
*/
public static int getOrientation(Point2D.Double a, Point2D.Double b, Point2D.Double c) {
return signum((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
}
private static class RadialSorter implements Comparator<Point2D.Double> {
private Point2D.Double origin;
public RadialSorter(Point2D.Double origin) {
this.origin = origin;
}
@Override
public int compare(Point2D.Double p1, Point2D.Double p2) {
return polarCompare(origin, p1, p2);
}
private static int polarCompare(Point2D.Double o, Point2D.Double p, Point2D.Double q) {
double dxp = p.x - o.x;
double dyp = p.y - o.y;
double dxq = q.x - o.x;
double dyq = q.y - o.y;
int orient = getOrientation(o, p, q);
if (orient == COUNTERCLOCKWISE) {
return 1;
}
if (orient == CLOCKWISE) {
return -1;
}
// collinear
double op = dxp * dxp + dyp * dyp;
double oq = dxq * dxq + dyq * dyq;
return (op < oq) ? -1 : (op > oq) ? 1 : 0;
}
}
}