Java Convex Polygon benchmark code
authorBlackMinou <romain.chanoir@viacesi.fr>
Fri, 22 May 2015 09:25:01 +0000 (11:25 +0200)
committerBlackMinou <romain.chanoir@viacesi.fr>
Wed, 27 May 2015 17:30:09 +0000 (19:30 +0200)
Signed-off-by: BlackMinou <romain.chanoir@viacesi.fr>
Signed-off-by: Djomanix <johan.kayser@viacesi.fr>

benchmarks/polygons/java/code/AntiClockSort.java [new file with mode: 0644]
benchmarks/polygons/java/code/BenchPolygon.java [new file with mode: 0644]
benchmarks/polygons/java/code/ClockSort.java [new file with mode: 0644]
benchmarks/polygons/java/code/ConvexPolygon.java [new file with mode: 0644]
benchmarks/polygons/java/code/PointDouble.java [new file with mode: 0644]
benchmarks/polygons/java/code/PointXCompare.java [new file with mode: 0644]
benchmarks/polygons/java/code/PolygonSorter.java [new file with mode: 0644]
benchmarks/polygons/java/code/Projection.java [new file with mode: 0644]

diff --git a/benchmarks/polygons/java/code/AntiClockSort.java b/benchmarks/polygons/java/code/AntiClockSort.java
new file mode 100644 (file)
index 0000000..e698d62
--- /dev/null
@@ -0,0 +1,53 @@
+
+/**
+ *
+ * @author Johan
+ * @source
+ * http://stackoverflow.com/questions/6989100/sort-points-in-clockwise-order
+ */
+public class AntiClockSort extends PolygonSorter {
+
+    public AntiClockSort(double[][] points) {
+        super(points);
+    }
+
+    /**
+     * Compare polygon vertices in counter-clock wise order starting at six
+     * hour. If two points share the same rad, then the farest to the center is
+     * chosen.
+     *
+     * @param a: a point to compare
+     * @param b: a second point to compare
+     * @return
+     */
+    @Override
+    public int compare(PointDouble a, PointDouble b) {
+        if (a.x - center.x >= 0 && b.x - center.x < 0) {
+            return -1;
+        }
+        if (a.x - center.x < 0 && b.x - center.x >= 0) {
+            return +1;
+        }
+        if (a.x - center.x == 0 && b.x - center.x == 0) {
+            if (a.y - center.y >= 0 || b.y - center.y >= 0) {
+                return (a.y > b.y) ? -1 : +1;
+            }
+            return (b.y > a.y) ? -1 : +1;
+        }
+
+        // compute the cross product of vectors (center -> a) x (center -> b)
+        double det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
+        if (det < 0) {
+            return -1;
+        }
+        if (det > 0) {
+            return +1;
+        }
+
+        // points a and b are on the same line from the center
+        // check which point is closer to the center
+        double d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
+        double d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
+        return (d1 > d2) ? -1 : +1;
+    }
+}
diff --git a/benchmarks/polygons/java/code/BenchPolygon.java b/benchmarks/polygons/java/code/BenchPolygon.java
new file mode 100644 (file)
index 0000000..bb7721f
--- /dev/null
@@ -0,0 +1,147 @@
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Random;
+
+/**
+ *
+ * @author Johan Kayser, Romain Chanoir
+ */
+/**
+ * Runs the benchmarks for the most important operations and generates a file
+ * with time execution results
+ */
+public class BenchPolygon {
+
+    public static void main(String[] args) throws IOException {
+           int  n = 0;
+           if(args[1] != null){
+               n = Integer.parseInt(args[1]);
+           }else {
+               n = 100000;
+           }
+       switch (args[0]){
+               case "add_vertex":
+                       testAddVertex(n);
+                       break;
+               case "sort_vertices":
+                       testSortVertices(n);
+                       break;
+               case "intersection":
+                       testIntersects(n);
+                       break;
+               case "convex_hull":
+                       testConvexHull(n);
+                       break;
+               case "convexity":
+                       testIsConvex(n);
+                       break;
+               case "contain":
+                       testContain(n);
+                       break;
+               default:
+                       break;
+       }
+    }
+
+    /**
+     * addVertex bench: adds a vertex to a polygon with the given number of
+     * vertices
+     */
+    public static void testAddVertex(int nb) throws IOException {
+        ArrayList<PointDouble> points = new ArrayList<>();
+        ArrayList<PointDouble> randomPoints = new ArrayList<>();
+        randomPoints = generatePoints(nb + 1);
+
+        for (int i = 0; i < nb; ++i) {
+            points.add(randomPoints.remove(0));
+        }
+        ConvexPolygon test = new ConvexPolygon(points);
+        test.sortVertices(new AntiClockSort(test.getVertices()));
+
+        test.addVertex(randomPoints.remove(0));
+    }
+
+    /**
+     * sortVertices bench: sorts the given number of vertices in the ArrayList
+     * of a polygon
+     */
+    public static void testSortVertices(int nb) throws IOException {
+        ArrayList<PointDouble> randomPoints = new ArrayList<>();
+        randomPoints = generatePoints(nb);
+        Collections.shuffle(randomPoints);
+
+        ConvexPolygon test = new ConvexPolygon(randomPoints);
+        test.sortVertices(new AntiClockSort(test.getVertices()));
+
+    }
+
+    /**
+     * intersects bench: tests the intersection between two polygons with the
+     * given number of vertices
+     */
+    public static void testIntersects(int nb) throws IOException {
+        ArrayList<PointDouble> points1 = new ArrayList<>();
+        ArrayList<PointDouble> points2 = new ArrayList<>();
+        points1 = generatePoints(nb);
+        points2 = generatePoints(nb);
+        ConvexPolygon test1 = new ConvexPolygon(points1);
+        ConvexPolygon test2 = new ConvexPolygon(points2);
+        test1.sortVertices(new AntiClockSort(test1.getVertices()));
+        test2.sortVertices(new AntiClockSort(test2.getVertices()));
+
+        Boolean rez = test1.intersects(test2);
+    }
+
+    /**
+     * convexHull bench: gets the convex hull of the given number of points
+     */
+    public static void testConvexHull(int nb) throws IOException {
+        ArrayList<PointDouble> randomPoints = new ArrayList<>();
+        randomPoints = generatePoints(nb);
+        Collections.shuffle(randomPoints);
+        ConvexPolygon test = new ConvexPolygon(randomPoints);
+
+        ConvexPolygon rez = test.convexHull(randomPoints);
+    }
+
+    /**
+     * isConvex bench: checks if the polygon with the given number of vertices
+     * is convex (we test the worst case -> polygon vertices are ordered)
+     */
+    public static void testIsConvex(int nb) throws IOException {
+        ArrayList<PointDouble> randomPoints = new ArrayList<>();
+        randomPoints = generatePoints(nb);
+        ConvexPolygon test = new ConvexPolygon(randomPoints);
+        test.sortVertices(new AntiClockSort(test.getVertices()));
+
+        Boolean rez = test.isConvex();
+    }
+
+    /**
+     * contain bench: checks if the polygon with the given number of vertices
+     * contains a randomly generated point
+     */
+    public static void testContain(int nb) throws IOException {
+        ArrayList<PointDouble> randomPoints = new ArrayList<>();
+        randomPoints = generatePoints(nb);
+        ConvexPolygon test = new ConvexPolygon(randomPoints);
+        test.sortVertices(new AntiClockSort(test.getVertices()));
+
+        Boolean rez = test.contain(new PointDouble(0.0, 0.0));
+    }
+
+    /**
+     * generates some points making it easier to use convex polygons
+     */
+    public static ArrayList<PointDouble> generatePoints(int nb) {
+        ArrayList<PointDouble> pts = new ArrayList<>();
+        pts = PointDouble.getNPointsOnCircle(100.0, nb);
+        return pts;
+    }
+}
diff --git a/benchmarks/polygons/java/code/ClockSort.java b/benchmarks/polygons/java/code/ClockSort.java
new file mode 100644 (file)
index 0000000..617b194
--- /dev/null
@@ -0,0 +1,50 @@
+
+/**
+ *
+ * @author Johan
+ */
+public class ClockSort extends PolygonSorter {
+
+    public ClockSort(double[][] points) {
+        super(points);
+    }
+
+    /**
+     * Compare polygon vertices in clock wise order starting at six hour. If two
+     * points share the same rad, then the farest to the center is chosen.
+     *
+     * @param a: a point to compare
+     * @param b: a second point to compare
+     * @return
+     */
+    @Override
+    public int compare(PointDouble a, PointDouble b) {
+        if (a.x - center.x >= 0 && b.x - center.x < 0) {
+            return +1;
+        }
+        if (a.x - center.x < 0 && b.x - center.x >= 0) {
+            return -1;
+        }
+        if (a.x - center.x == 0 && b.x - center.x == 0) {
+            if (a.y - center.y >= 0 || b.y - center.y >= 0) {
+                return (a.y > b.y) ? +1 : -1;
+            }
+            return (b.y > a.y) ? +1 : -1;
+        }
+
+        // compute the cross product of vectors (center -> a) x (center -> b)
+        double det = (a.x - center.x) * (b.y - center.y) - (b.x - center.x) * (a.y - center.y);
+        if (det < 0.0d) {
+            return +1;
+        }
+        if (det > 0.0d) {
+            return -1;
+        }
+
+        // points a and b are on the same line from the center
+        // check which point is closer to the center
+        double d1 = (a.x - center.x) * (a.x - center.x) + (a.y - center.y) * (a.y - center.y);
+        double d2 = (b.x - center.x) * (b.x - center.x) + (b.y - center.y) * (b.y - center.y);
+        return (d1 > d2) ? +1 : -1;
+    }
+}
diff --git a/benchmarks/polygons/java/code/ConvexPolygon.java b/benchmarks/polygons/java/code/ConvexPolygon.java
new file mode 100644 (file)
index 0000000..e02bc57
--- /dev/null
@@ -0,0 +1,280 @@
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ *
+ * @author romis_000, Johan
+ */
+public class ConvexPolygon {
+
+    private ArrayList<PointDouble> points = new ArrayList<PointDouble>();
+
+    public ConvexPolygon(ArrayList<PointDouble> points) {
+        this.points = points;
+    }
+
+    public ArrayList<PointDouble> getPointDoubles() {
+        return points;
+    }
+
+    public double[] GetXPointDoubles() {
+        double xpoints[] = new double[this.points.size()];
+        int c = 0;
+        for (PointDouble p : this.points) {
+            xpoints[c] = p.x;
+            ++c;
+        }
+        return xpoints;
+    }
+
+    public double[] GetYPointDoubles() {
+        double ypoints[] = new double[this.points.size()];
+        int c = 0;
+        for (PointDouble p : this.points) {
+            ypoints[c] = p.y;
+            ++c;
+        }
+        return ypoints;
+    }
+
+    public double[][] getVertices() {
+        double[][] vertices = new double[points.size()][2];
+        for (int i = 0; i < points.size(); ++i) {
+            vertices[i][0] = points.get(i).x;
+            vertices[i][1] = points.get(i).y;
+        }
+        return vertices;
+    }
+
+    /**
+     * Returns the axis corresponding to the edges of the polygon, used to check
+     * for detection collision
+     */
+    public PointDouble[] Getaxes() {
+        PointDouble[] axes = new PointDouble[this.points.size()];
+        for (int i = 0; i < this.points.size(); i++) {
+            // get the current vertex
+            PointDouble v1 = new PointDouble(this.points.get(i).getX(), this.points.get(i).getY());
+            // get the next vertex
+            PointDouble v2 = new PointDouble(this.points.get(i + 1 == this.points.size() ? 0 : i + 1).getX(), this.points.get(i + 1 == this.points.size() ? 0 : i + 1).getY());
+            // subtract the two to get the edge vector
+            PointDouble edge = new PointDouble((v1.x + (-v2.x)), (v1.y + (-v2.y)));
+            // get either perpendicular vector
+            PointDouble normal = new PointDouble((-edge.y), edge.x);
+            // the perp method is just (x, y) => (-y, x) or (y, -x)
+            axes[i] = normal;
+        }
+        return axes;
+    }
+
+    /**
+     * Checks if the polygon is convex
+     */
+    public boolean isConvex() {
+        PointDouble prev = points.get(points.size() - 2);
+        PointDouble curr = points.get(points.size() - 1);
+        PointDouble next = points.get(0);
+        // Are the first two selected edges making a turnleft ?
+        boolean isCCW = turnLeft(prev, curr, next);
+        // Verify if all the edges are making the same type of angle as the first two
+        for (int i = 1; i < points.size(); i++) {
+            prev = curr;
+            curr = next;
+            next = points.get(i);
+            if (turnLeft(prev, curr, next) != isCCW) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks if a given point is within the polygon
+     */
+    public boolean contain(PointDouble p) {
+        PointDouble prev = points.get(points.size() - 1);
+        PointDouble curr = p;
+        PointDouble next = points.get(0);
+        // Is the point left or right of the selected edge ?
+        boolean isCCW = turnLeft(prev, curr, next);
+        // Is the point the same side of every other edges of the polygon ?
+        for (int i = 1; i < points.size(); i++) {
+            prev = next;
+            next = points.get(i);
+            if (turnLeft(prev, curr, next) != isCCW) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check the sign of the angle between vectors [p1, p2] and [p2, p3]
+     * with the cross product
+     */
+    public boolean turnLeft(PointDouble p1, PointDouble p2, PointDouble p3) {
+        return ((p2.getX() - p1.getX()) * (p3.getY()
+                - p2.getY()) - (p3.getX() - p2.getX()) * (p2.getY() - p1.getY())) > 0;
+    }
+
+    /**
+     * Checks if the vertices of the polygon are in counter-clock wise order
+     * The vertices of the polygon need to be sorted for this test
+     */
+    public boolean isCCW() {
+        double min = points.get(0).getY();
+        int minIndex = 0;
+        for (int i = 1; i < points.size() - 1; i++) {
+            if (points.get(i).getY() < min) {
+                min = points.get(i).getY();
+                minIndex = i;
+            }
+        }
+        PointDouble prev = points.get((minIndex - 1 + points.size()) % points.size());
+        PointDouble next = points.get((minIndex + 1) % points.size());
+        return turnLeft(prev, points.get(minIndex), next);
+    }
+
+    /**
+     * Calculates the convex hull of list of points
+     * Using the monotone chain algorithm
+     */
+    public ConvexPolygon convexHull(ArrayList<PointDouble> points) {
+        Collections.sort(points, new PointXCompare());
+        int n = points.size();
+
+        ArrayList<PointDouble> pl1 = new ArrayList<PointDouble>();
+        ArrayList<PointDouble> pl2 = new ArrayList<PointDouble>();
+        for (int i = 0; i < n; i++) {
+            while (pl2.size() >= 2 && !(turnLeft(pl2.get(pl2.size() - 2), pl2.get(pl2.size() - 1), points.get(i)))) {
+                pl2.remove(pl2.get(pl2.size() - 1));
+            }
+            pl2.add(points.get(i));
+        }
+        for (int i = n - 1; i >= 0; i--) {
+            while (pl1.size() >= 2 && !(turnLeft(pl1.get(pl1.size() - 2), pl1.get(pl1.size() - 1), points.get(i)))) {
+                pl1.remove(pl1.get(pl1.size() - 1));
+            }
+            pl1.add(points.get(i));
+        }
+        pl1.remove(pl1.size() - 1);
+        pl2.remove(pl2.size() - 1);
+        pl2.addAll(pl1);
+        return new ConvexPolygon(pl2);
+    }
+
+    /**
+     * Translates the polygon from the given numbers
+     */
+    public void translate(double x, double y) {
+        for (PointDouble p : this.points) {
+            p.x += x;
+            p.y += y;
+        }
+    }
+
+    /**
+     * Checks for an intersection between the polygon and the second given
+     * polygon
+     */
+    public Boolean intersects(ConvexPolygon pol2) {
+        PointDouble[] axes1 = this.Getaxes();
+        PointDouble[] axes2 = pol2.Getaxes();
+        for (int i = 0; i < axes1.length; i++) {
+            PointDouble axis = axes1[i];
+            // project both shapes onto the axis
+            Projection p1 = this.project(axis);
+            Projection p2 = pol2.project(axis);
+            // do the projections overlap?
+            if (!p1.overlap(p2)) {
+                // then we can guarantee that the shapes do not overlap
+                return false;
+            }
+        }
+        for (int i = 0; i < axes2.length; i++) {
+            PointDouble axis = axes2[i];
+            Projection p1 = this.project(axis);
+            Projection p2 = pol2.project(axis);
+            if (!p1.overlap(p2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Generates a projection of an edge of the polygon on a given axis
+     */
+    public Projection project(PointDouble axis) {
+        double min = ((axis.x * this.points.get(0).x) + (axis.y * this.points.get(0).y));
+        double max = min;
+        for (int i = 1; i < this.points.size(); i++) {
+            double p = ((axis.x * this.points.get(i).x) + (axis.y * this.points.get(i).y));
+            if (p < min) {
+                min = p;
+            } else if (p > max) {
+                max = p;
+            }
+        }
+        Projection proj = new Projection(min, max);
+        return proj;
+    }
+
+    public Boolean hasVertex(PointDouble pt) {
+        for (int i = 0; i < this.points.size(); i++) {
+            //for (int i = 0; i < this.points.size() - 1; i++) {
+            if (this.points.get(i).equals(pt)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Boolean deleteVertex(PointDouble pt) {
+        if (this.points.size() > 3) {
+            for (int i = 0; i < this.points.size(); i++) {
+                if (this.points.get(i).equals(pt)) {
+                    this.points.remove(i);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sorts the vertices of the polygon in an order specified by the sorter
+     * used
+     */
+    public void sortVertices(PolygonSorter sorter) {
+        PointDouble[] arrPointDoubles = points.toArray(new PointDouble[points.size()]);
+        Arrays.sort(arrPointDoubles, sorter);
+        this.points.clear();
+        points.addAll(Arrays.asList(arrPointDoubles));
+    }
+
+    /*
+    * Add a vertex to the polygon
+    * Return true if the vertex is added to the polygon
+    * Return false otherwise, which means that the addition
+    * of the vertex would have made it concave.
+    */
+    public Boolean addVertex(PointDouble pt) {
+        // Make a temporary list to check some properties of the new polygon
+        ArrayList<PointDouble> tempList = new ArrayList<>(Arrays.asList(this.points.toArray(new PointDouble[this.points.size()])));
+        tempList.add(pt);
+        // Create a temporary polygon
+        ConvexPolygon tempPolygon = new ConvexPolygon(tempList);
+        // Sort it
+        tempPolygon.sortVertices(new AntiClockSort(tempPolygon.getVertices()));
+        // We need the new polygon to be convex, or we can't accept to add the new vertex.
+        if (tempPolygon.isConvex()) {
+            this.points = tempPolygon.points;
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/benchmarks/polygons/java/code/PointDouble.java b/benchmarks/polygons/java/code/PointDouble.java
new file mode 100644 (file)
index 0000000..8e1b0a4
--- /dev/null
@@ -0,0 +1,60 @@
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Represents a 2d point with double coordinates
+ *
+ * @author Johan
+ */
+public class PointDouble {
+
+    double x;
+    double y;
+
+    public PointDouble() {
+        this(0.0d, 0.0d);
+    }
+
+    public PointDouble(double x, double y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public double getX() {
+        return this.x;
+    }
+
+    public double getY() {
+        return this.y;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final PointDouble other = (PointDouble) obj;
+        if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) {
+            return false;
+        }
+        if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * returns a array of n points on a circle
+     */
+    public static ArrayList<PointDouble> getNPointsOnCircle(double radius, int n) {
+        ArrayList<PointDouble> points = new ArrayList<>();
+       Random generator = new Random(0);
+        for(int i = 0; i < n; i++){
+       double angle = generator.nextFloat() * Math.PI * 2;
+               PointDouble p = new PointDouble(Math.cos(angle) * radius, Math.sin(angle) * radius);
+       points.add(p);
+       }
+        return points;
+    }
+}
diff --git a/benchmarks/polygons/java/code/PointXCompare.java b/benchmarks/polygons/java/code/PointXCompare.java
new file mode 100644 (file)
index 0000000..34f4520
--- /dev/null
@@ -0,0 +1,21 @@
+
+import java.util.Comparator;
+
+/**
+ *
+ * @author romis_000
+ */
+class PointXCompare
+        implements Comparator<PointDouble> {
+
+    @Override
+    public int compare(final PointDouble a, final PointDouble b) {
+        if (a.x == b.x) {
+            //return (int) (a.y - b.y);
+            return (a.y > b.y) ? -1 : +1;
+        } else {
+            //return (int)(a.x - b.x);
+            return (a.x > b.x) ? -1 : +1;
+        }
+    }
+}
diff --git a/benchmarks/polygons/java/code/PolygonSorter.java b/benchmarks/polygons/java/code/PolygonSorter.java
new file mode 100644 (file)
index 0000000..e05b984
--- /dev/null
@@ -0,0 +1,33 @@
+
+import java.util.Comparator;
+
+/**
+ * An utility class to sort the polygon vertices, is extended by AntiClockSort
+ * and ClockSort
+ *
+ * @author Johan
+ */
+public abstract class PolygonSorter implements Comparator<PointDouble> {
+
+    PointDouble center;
+
+    public PolygonSorter(double[][] podoubles) {
+        this.center = calcCenter(podoubles);
+    }
+
+    /**
+     * returns the point representing the center of a polygon
+     */
+    final PointDouble calcCenter(double[][] podoubles) {
+        double sumx = 0;
+        double sumy = 0;
+        for (double[] podouble : podoubles) {
+            sumx += podouble[0];
+            sumy += podouble[1];
+        }
+        return new PointDouble(sumx / podoubles.length, sumy / podoubles.length);
+    }
+
+    @Override
+    public abstract int compare(PointDouble a, PointDouble b);
+}
diff --git a/benchmarks/polygons/java/code/Projection.java b/benchmarks/polygons/java/code/Projection.java
new file mode 100644 (file)
index 0000000..e18688e
--- /dev/null
@@ -0,0 +1,21 @@
+
+/**
+ * An utility class to store a projection of an edge on an axis, used by the
+ * intersects operation
+ *
+ * @author Johan
+ */
+public class Projection {
+
+    private double min;
+    private double max;
+
+    public Projection(double min, double max) {
+        this.min = min;
+        this.max = max;
+    }
+
+    public Boolean overlap(Projection p2) {
+        return !(this.min > p2.max || p2.min > this.max);
+    }
+}