/*
* Copyright (c) 2010 Georgios Migdos <cyberpython@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.geogebra.common.kernel.discrete.geom.algorithms;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.geogebra.common.kernel.discrete.geom.LineAndPointUtils;
import org.geogebra.common.kernel.discrete.geom.Point2D;
import org.geogebra.common.kernel.discrete.geom.Segment2D;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.LogEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.grahams_scan.GrahamsScanCompleteEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.grahams_scan.GrahamsScanSegmentAddEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.grahams_scan.GrahamsScanSegmentCheckEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.grahams_scan.GrahamsScanSegmentRemoveEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.jarvis_march.JarvisAddSegmentEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.jarvis_march.JarvisChainsDetectedEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.jarvis_march.JarvisPointSelectedEvent;
import org.geogebra.common.kernel.discrete.geom.algorithms.logging.convex_hull.jarvis_march.JarvisPointsCheckEvent;
/**
*
* @author cyberpython
*/
public class ConvexHull {
static class PointComparator implements Comparator<Point2D> {
/*
* Sorts the points so that the lowest - leftmost one is the first. Used
* by both Graham's and Jarvis's algorithms.
*/
@Override
public int compare(Point2D o1, Point2D o2) {
double y1 = o1.getY();
double y2 = o2.getY();
double yDiff = y1 - y2;
double errorTolerance = LineAndPointUtils.getErrorTolerance();
if (yDiff < -errorTolerance) {
return -1;
} else if (yDiff > errorTolerance) {
return 1;
} else {
double x1 = o1.getX();
double x2 = o2.getX();
double xDiff = x1 - x2;
if (xDiff < -errorTolerance) {
return -1;
} else if (xDiff > errorTolerance) {
return 1;
} else {
return 0;
}
}
}
}
/*
* static class PointComparator implements Comparator<Point2D> {
*
* public int compare(Point2D o1, Point2D o2) { double y1 = o1.getY();
* double y2 = o2.getY(); if (y1 < y2) { return -1; } else if (y1 == y2) {
* double x1 = o1.getX(); double x2 = o2.getX();
*
* if (x1 < x2) { return -1; } else if (x1 == x2) { return 0; } else {
* return 1; } } else { return 1; } } }
*/
static class PolarAngleComparator implements Comparator<Point2D> {
private Point2D p0;
public PolarAngleComparator(Point2D p0) {
this.p0 = p0;
}
/*
* Sorts the points so that the lowest - leftmost one is the first. Used
* by both Graham's and Jarvis's algorithms.
*/
@Override
public int compare(Point2D p1, Point2D p2) {
Double theta1 = polarAngle(p0, p1);
Double theta2 = polarAngle(p0, p2);
if (theta1 == null) {
theta1 = new Double(0);
}
if (theta2 == null) {
theta2 = new Double(0);
}
if (p1.equals(p0)) {
theta1 = -500.0;
}
if (p2.equals(p0)) {
theta2 = -500.0;
}
if (theta1 < theta2) {
return -1;
} else if (Math.abs(
theta1.doubleValue() - theta2.doubleValue()) < 10E-15) {
double errorTolerance = LineAndPointUtils.getErrorTolerance();
double x1 = p1.getX();
double x2 = p2.getX();
double xDiff = x1 - x2;
if (xDiff < -errorTolerance) {
return -1;
} else if (xDiff > errorTolerance) {
return 1;
} else {
return 0;
}
} else {
return 1;
}
}
}
static Double polarAngle(Point2D p0, Point2D p1) {
if (p0.equals(p1)) {
return null;
}
double dy = p1.getY() - p0.getY();
double dx = p1.getX() - p0.getX();
double result = Math.toDegrees(Math.atan(dy / dx));
if (dx < 0) {
result = 180 + result;
} else {
if (dy < 0) {
result = 270 + result;
}
}
return result;
}
private static Double polarAngleNegAxis(Point2D p0, Point2D p1) {
if (p0.equals(p1)) {
return null;
}
double dy = p1.getY() - p0.getY();
double dx = p1.getX() - p0.getX();
double result = Math.toDegrees(Math.atan(dy / dx));
if (dx < 0) {
result = 180 + result;
}
result = (result + 180) % 360;
return result;
}
private static int jarvisFindSmallestPolarAngle(List<Point2D> points,
Point2D p0, boolean rightChain, List<LogEvent> events) {
/*
* Returns the index of the point with the smallest polar angle relative
* to p0 The rightChain parameter defines whether we are looking for the
* smallest polar angle in the left or the right chain of the hull.
*/
int i = 0;
int index = 0;
double minAngle = 500;
Double polarAngle;
for (Iterator<Point2D> it = points.iterator(); it.hasNext();) {
Point2D point = it.next();
if (rightChain) {
polarAngle = polarAngle(p0, point);
} else {
polarAngle = polarAngleNegAxis(p0, point);
}
if (polarAngle != null) {
events.add(new JarvisPointsCheckEvent(p0, point, polarAngle,
rightChain));
}
if ((polarAngle != null) && (polarAngle < minAngle)) {
index = i;
minAngle = polarAngle;
}
i++;
}
events.add(new JarvisPointSelectedEvent(p0, points.get(index), minAngle,
rightChain));
return index;
}
public static List<Point2D> jarvisMarch(List<Point2D> points,
List<LogEvent> events) {
events.clear();
List<Point2D> result = new ArrayList<Point2D>();
if (points.size() > 2) {
Collections.sort(points, new PointComparator());
Point2D lowestPoint = points.get(0);
Point2D highestPoint = points.get(points.size() - 1);
events.add(
new JarvisChainsDetectedEvent(lowestPoint, highestPoint));
result.add(lowestPoint);
events.add(new JarvisAddSegmentEvent(lowestPoint, lowestPoint));
Point2D p = lowestPoint;
Point2D p1;
int index = 1;
while (!p.equals(highestPoint)) {
index = jarvisFindSmallestPolarAngle(points, p, true, events);
result.add(points.get(index));
p1 = points.get(index);
events.add(new JarvisAddSegmentEvent(p, p1));
p = p1;
}
while (!p.equals(lowestPoint)) {
index = jarvisFindSmallestPolarAngle(points, p, false, events);
result.add(points.get(index));
p1 = points.get(index);
events.add(new JarvisAddSegmentEvent(p, p1));
p = p1;
}
}
return result;
}
private static double ccw(Point2D p1, Point2D p2, Point2D p3) {
/*
* Three points are a counter-clockwise turn if ccw > 0, clockwise if
* ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
* gives the signed area of the triangle formed by p1, p2, and p3.
*/
return (p2.getX() - p1.getX()) * (p3.getY() - p1.getY())
- (p2.getY() - p1.getY()) * (p3.getX() - p1.getX());
}
// public static List<Point2D> grahamsScan(List<Point2D> points,
// List<LogEvent> events) {
//
// events.clear();
//
// List<Point2D> result = new ArrayList<Point2D>();
//
// if (points.size() > 2) {
//
// //----------------- Start of Graham's Scan
//
// ArrayDeque<Point2D> s = new ArrayDeque<Point2D>();
//
// Collections.sort(points, new PointComparator());
// Point2D startingPoint = points.get(0);
//
// for(int i=0; i<30; i++){
// System.out.println();
// }
// int j=0;
// boolean stop=false;
// while(j<points.size() && !stop){
// Point2D p = points.get(j);
// if(p.getY()>startingPoint.getY()){
// break;
// }
// if(p.equals(startingPoint)){
// System.out.print("S--> ");
// }
// System.out.println(p.toString());
// j++;
// }
//
// Collections.sort(points, new PolarAngleComparator(startingPoint));
//
// s.push(startingPoint);
// s.push(points.get(1));
// s.push(points.get(2));
//
// Point2D top = points.get(2);
// Point2D nTop = points.get(1);
//
// events.add(new GrahamsScanSegmentAddEvent(new Segment2D(points.get(0),
// nTop)));
// events.add(new GrahamsScanSegmentAddEvent(new Segment2D(nTop, top)));
//
// int m = points.size();
// for (int i = 3; i < m; i++) {
//
// events.add(new GrahamsScanSegmentCheckEvent(new Segment2D(nTop, top), new
// Segment2D(top, points.get(i))));
// while ((s.size()>1) && ccw(nTop, top, points.get(i)) <= 0) {
//
// events.add(new GrahamsScanSegmentRemoveEvent(new Segment2D(nTop, top)));
// s.pop();
//
// nTop = peekNTop(s);
// top = s.peekFirst();
//
// if(s.size()>1){
// events.add(new GrahamsScanSegmentCheckEvent(new Segment2D(nTop, top), new
// Segment2D(top, points.get(i))));
// }
// }
//
// s.push(points.get(i));
//
// nTop = peekNTop(s);
// top = s.peekFirst();
//
// events.add(new GrahamsScanSegmentAddEvent(new Segment2D(nTop, top)));
//
// }
//
// s.push(startingPoint);
//
// events.add(new GrahamsScanSegmentAddEvent(new Segment2D(top,
// points.get(0))));
// events.add(new GrahamsScanCompleteEvent());
//
// //----------------- End of Graham's Scan
//
// for (Iterator<Point2D> it = s.descendingIterator(); it.hasNext();) {
// Point2D point2D = it.next();
// result.add(point2D);
// }
//
//
// }
// return result;
//
// }
public static List<Point2D> grahamsScan2(List<Point2D> points,
List<LogEvent> events) {
events.clear();
List<Point2D> result = new ArrayList<Point2D>();
if (points.size() > 2) {
// ----------------- Start of Graham's Scan
Collections.sort(points, new PointComparator());
Point2D startingPoint = points.get(0);
for (int i = 0; i < 30; i++) {
System.out.println();
}
int j = 0;
boolean stop = false;
while (j < points.size() && !stop) {
Point2D p = points.get(j);
if (p.getY() > startingPoint.getY()) {
break;
}
System.out.println(p.toString());
j++;
}
Collections.sort(points, new PolarAngleComparator(startingPoint));
Point2D endPoint = points.get(points.size() - 1);
// System.out.println("PUSH: "+points.get(0));
// System.out.println("PUSH: "+points.get(1));
// System.out.println("ADD :"+points.get(0)+" -> "+points.get(1));
events.add(new GrahamsScanSegmentAddEvent(
new Segment2D(points.get(0), points.get(1))));
int n = points.size();
int m = 1;
for (int i = 2; i < n; i++) {
// System.out.println("CHECK :"+points.get(m - 1)+",
// "+points.get(m)+" , "+points.get(i));
events.add(new GrahamsScanSegmentCheckEvent(
new Segment2D(points.get(m - 1), points.get(m)),
new Segment2D(points.get(m), points.get(i))));
while ((m > 0) && ccw(points.get(m - 1), points.get(m),
points.get(i)) <= 0) {
// System.out.println("POP: "+points.get(m));
// System.out.println("REMOVE :"+points.get(m-1)+" ->
// "+points.get(m));
events.add(new GrahamsScanSegmentRemoveEvent(
new Segment2D(points.get(m - 1), points.get(m))));
m--;
// System.out.println("CHECK :"+points.get(m - 1)+",
// "+points.get(m)+" , "+points.get(i));
if (m > 0) {
events.add(new GrahamsScanSegmentCheckEvent(
new Segment2D(points.get(m - 1), points.get(m)),
new Segment2D(points.get(m), points.get(i))));
}
}
m++;
// swap(points, m, i);
Collections.swap(points, m, i);
// System.out.println("PUSH: "+points.get(m));
// System.out.println("ADD :"+points.get(m-1)+" ->
// "+points.get(m));
events.add(new GrahamsScanSegmentAddEvent(
new Segment2D(points.get(m - 1), points.get(m))));
}
// System.out.println("PUSH: "+points.get(0));
// System.out.println("ADD :"+points.get(m)+" -> "+points.get(0));
events.add(new GrahamsScanSegmentAddEvent(
new Segment2D(points.get(m), points.get(0))));
events.add(new GrahamsScanCompleteEvent());
// ----------------- End of Graham's Scan
int i = 0;
Point2D p = null;
int max = points.size();
while ((i < max) && (p != endPoint)) {
p = points.get(i);
result.add(p);
i++;
}
result.add(startingPoint);
}
return result;
}
/*
* public static void main(String[] args) { List<Point2D> points = new
* ArrayList(); points.add(new Point(7, 6)); points.add(new Point(3, 7));
* points.add(new Point(2, 3)); //points.add(new Point(8, 8));
* //points.add(new Point(6, 5)); points.add(new Point(11, 4));
* points.add(new Point(5, 1)); //points.add(new Point(1, 4));
* points.add(new Point(8, 3)); points.add(new Point(5, 2));
*
* List<Point2D> jarvisResult = ConvexHull.jarvisMarch(points, new
* ArrayList<LogEvent>()); List<Point2D> grahamsResult =
* ConvexHull.grahamsScan(points, new ArrayList<LogEvent>());
*
* System.out.print("Jarvis' March:\t");
* LineAndPointUtils.printPointsList(jarvisResult, System.out);
* System.out.println(); System.out.print("Graham's Scan:\t");
* LineAndPointUtils.printPointsList(grahamsResult, System.out);
*
* }
*/
}