package com.vitco.low.triangulate; import com.vitco.Main; import com.vitco.low.triangulate.util.Grid2PolyHelper; import com.vitco.low.triangulate.util.HopcroftKarp; import org.poly2tri.geometry.polygon.PolygonPoint; import org.poly2tri.triangulation.delaunay.DelaunayTriangle; import java.awt.*; import java.util.*; /** * Implements an algorithm that optimally covers the surface with rectangles. * * -> Optimal rectangle coverage * * Reference: http://arxiv.org/pdf/0908.3916.pdf */ public class Grid2TriGreedyOptimal { // compute the triangulation public static ArrayList<DelaunayTriangle> triangulate(boolean[][] bits) { // result list ArrayList<DelaunayTriangle> result = new ArrayList<DelaunayTriangle>(); // find some basic parameters short lenX = (short) bits.length; short lenY = (short) bits[0].length; // get polygons short[][][] polys = Grid2PolyHelper.convert(bits); // loop over polygons for (short[][] poly : polys) { // extract all points in this polygon HashSet<Point> pointList = new HashSet<Point>(); for (short[] outline : poly) { for (int i = 0; i < outline.length; i+=2) { pointList.add(new Point(outline[i], outline[i+1])); } } // find the concave points (vertices) HashSet<Point> concavePointList = new HashSet<Point>(pointList); for (Iterator<Point> it = concavePointList.iterator(); it.hasNext();) { Point p = it.next(); // check if concave boolean concave = p.x != 0 && p.y != 0 && p.x != lenX && p.y != lenY && (bits[p.x][p.y]?1:0) + (bits[p.x - 1][p.y]?1:0) + (bits[p.x][p.y - 1]?1:0) + (bits[p.x - 1][p.y - 1]?1:0) == 3; if (!concave) { it.remove(); } } // find the "good diagonals" ArrayList<Point[]> diagonals = new ArrayList<Point[]>(); for (Point p1 : concavePointList) { for (Point p2 : concavePointList) { if (p1.x == p2.x && p1.y < p2.y) { // check if they are connected by a line boolean connected = true; for (int i = Math.min(p1.y, p2.y), len = Math.max(p1.y, p2.y); i < len; i++) { if (!bits[p1.x][i] || !bits[p1.x-1][i]) { connected = false; break; } } if (connected) { diagonals.add(new Point[] {p1,p2}); } } if (p1.y == p2.y && p1.x < p2.x) { // check if they are connected by a line boolean connected = true; for (int i = Math.min(p1.x, p2.x), len = Math.max(p1.x, p2.x); i < len; i++) { if (!bits[i][p1.y] || !bits[i][p1.y-1]) { connected = false; break; } } if (connected) { diagonals.add(new Point[] {p1,p2}); } } } } // compute the bipartite graph mapping HashMap<Integer, ArrayList<Integer>> mapOtoU = new HashMap<Integer, ArrayList<Integer>>(); HashMap<Integer, ArrayList<Integer>> mapUtoO = new HashMap<Integer, ArrayList<Integer>>(); for (int i1 = 0; i1 < diagonals.size(); i1++) { Point[] d1 = diagonals.get(i1); ArrayList<Integer> mappedTo = new ArrayList<Integer>(); if (d1[0].x == d1[1].x) { mapOtoU.put(i1, mappedTo); } else { mapUtoO.put(i1, mappedTo); } for (int i2 = 0; i2 < diagonals.size(); i2++) { Point[] d2 = diagonals.get(i2); if (d1 != d2) { if (d1[0].x <= d2[1].x && d1[1].x >= d2[0].x && d1[0].y <= d2[1].y && d1[1].y >= d2[0].y) { mappedTo.add(i2); } } } } // // -- debug print // System.out.println("mapOtoU"); // for (Map.Entry<Integer, ArrayList<Integer>> entry : mapOtoU.entrySet()) { // System.out.print(entry.getKey()+1); // System.out.print(" -> "); // for (Integer mappedTo : entry.getValue()) { // System.out.print((mappedTo+1) + ", "); // } // System.out.println(); // } // System.out.println("------"); // // // -- debug print // System.out.println("mapUtoO"); // for (Map.Entry<Integer, ArrayList<Integer>> entry : mapUtoO.entrySet()) { // System.out.print(entry.getKey()+1); // System.out.print(" -> "); // for (Integer mappedTo : entry.getValue()) { // System.out.print((mappedTo+1) + ", "); // } // System.out.println(); // } // System.out.println("------"); // 1. find a maximum matching with Hopcroft-Karp HashMap<Integer, Integer> matching = HopcroftKarp.findMaximumMatching(mapOtoU); HashMap<Integer, Integer> revMatching = new HashMap<Integer, Integer>(); for (Map.Entry<Integer, Integer> entry : matching.entrySet()) { revMatching.put(entry.getValue(), entry.getKey()); } // // -- debug // System.out.println("Oben"); // for (Integer key : mapOtoU.keySet()) { // System.out.print((key+1) + ", "); // } // System.out.println(); // System.out.println("------"); // // // -- debug // System.out.println("Unten"); // for (Integer key : mapUtoO.keySet()) { // System.out.print((key+1) + ", "); // } // System.out.println(); // System.out.println("------"); // // // -- debug // System.out.println("Maximum Matching"); // for (Map.Entry<Integer, Integer> match : matching.entrySet()) { // System.out.println((match.getKey()+1) + " -> " + (match.getValue()+1)); // } // System.out.println("------"); // --------- // compute a maximal independent set using koenigs theorem HashSet<Integer> maxIndependentSet = computeMaximalIndependetSet(mapOtoU, mapUtoO, matching, revMatching); // // -- debug // System.out.println("Maximal Independent Set"); // for (Integer vertex : maxIndependentSet) { // System.out.print((vertex+1) + ", "); // } // System.out.println(); // System.out.println("------"); // ------------- // extract the point mappings for our edges HashMap<Point, Point> edgeMapping = new HashMap<Point, Point>(); for (Integer vertex : maxIndependentSet) { Point[] diag = diagonals.get(vertex); edgeMapping.put(diag[0], diag[1]); edgeMapping.put(diag[1], diag[0]); } // extract point information ArrayList<Point[]> pointInfoMap = new ArrayList<Point[]>(); for (short[] outline : poly) { Point prev = new Point(outline[outline.length - 4], outline[outline.length - 3]); Point cur = new Point(outline[0], outline[1]); for (int i = 2; i < outline.length; i += 2) { Point next = new Point(outline[i], outline[i + 1]); // check if opening or closing point pointInfoMap.add(new Point[] {cur, prev.y < cur.y || next.y > cur.y ? null : cur}); prev = cur; cur = next; } } // sort points "by x.y" Collections.sort(pointInfoMap, new Comparator<Point[]>() { private int sign; @Override public int compare(Point[] o1, Point[] o2) { sign = (int)Math.signum(o1[0].x - o2[0].x); if (sign != 0) { return sign; } else { return (int) Math.signum(o1[0].y - o2[0].y); } } }); // sweepline for rectangle (holds starting position of sweep) int[] sweepline = new int[lenY]; // state information for adding/removing (opening/closing edges) Integer adding = null; Integer removing = null; // number of rectangles in this polygon int rectangleCount = 0; // loop over ordered points for (Point[] points : pointInfoMap) { Point p = points[0]; // get the other point (if exists) Point otherPoint = edgeMapping.get(p); // true if this is part of a good edge boolean isPartOfGoodEdge = otherPoint != null; // true if this is a concave Point boolean concavePoint = concavePointList.contains(p); // check if this is an "opening point" boolean openingPoint = points[1] == null; // if this is a concave and (bad point or part of a vertical good line) if (concavePoint && (!isPartOfGoodEdge || (otherPoint.x == p.x))) { // check in both directions int add = -1; if (p.y > 0 && sweepline[p.y - 1] != 0) { add = 0; } else if (p.y < lenY && sweepline[p.y] != 0) { add = 1; } if (add != -1) { int val = sweepline[p.y - 1 + add]; if (val != 0 && val != p.x + 1) { // find edges and overwrite "depth" int miny = 0; for (int j = p.y - 1 + add; j > -1; j--) { if (sweepline[j] == val) { sweepline[j] = p.x + 1; } else { miny = j + 1; break; } } sweepline[p.y - 1 + add] = val; int maxy = lenY; for (int j = p.y - 1 + add; j < lenY; j++) { if (sweepline[j] == val) { sweepline[j] = p.x + 1; } else { maxy = j; break; } } // System.out.println(miny + " " + maxy + " @1 " + val); result.add(new DelaunayTriangle(new PolygonPoint(val - 1, maxy), new PolygonPoint(val - 1, miny), new PolygonPoint(p.x, miny))); result.add(new DelaunayTriangle(new PolygonPoint(p.x, miny), new PolygonPoint(p.x, maxy), new PolygonPoint(val - 1, maxy))); rectangleCount++; } } } if (openingPoint) { // update edge in sweepline (adding) if (adding != null) { for (int j = adding; j < p.y; j++) { sweepline[j] = p.x + 1; } adding = null; } else { adding = p.y; } } else { // update edge in sweepline (closing) if (removing != null) { int val = sweepline[removing]; if (val != 0) { for (int j = removing; j < p.y; j++) { sweepline[j] = 0; } if (val != p.x + 1) { // System.out.println(removing + " " + p.y + " @2 " + val); result.add(new DelaunayTriangle(new PolygonPoint(val - 1, p.y), new PolygonPoint(val - 1, removing), new PolygonPoint(p.x, removing))); result.add(new DelaunayTriangle(new PolygonPoint(p.x, removing), new PolygonPoint(p.x, p.y), new PolygonPoint(val - 1, p.y))); rectangleCount++; } } removing = null; } else { removing = p.y; } } // // -- debug // for (int aSweepline : sweepline) { // //System.out.print(String.format("%02x", aSweepline) + " "); // System.out.print(String.format("%1$-2s", aSweepline)); // } // System.out.println(" @ " + p.x + " " + p.y + " @ " + openingPoint); } // only do this check computation when running in JDK if (Main.isDebugMode()) { int verticeCount = 0; for (short[] outline : poly) { verticeCount += outline.length/2-1; } assert rectangleCount == verticeCount/2 + poly.length-1 - maxIndependentSet.size() - 1; } // // sweep over all points // for (Integer vertex : maxIndependentSet) { // System.out.println(" @ " + (vertex + 1)); // Point[] diag = diagonals.get(vertex); // } // //-- debug print // for (Point[] p : diagonals) { // result.add(new DelaunayTriangle(new PolygonPoint(p[0].x, p[0].y),new PolygonPoint(p[1].x, p[1].y),new PolygonPoint(p[0].x, p[0].y))); // } } return result; } // compute a maximal independent set using koenig's theorem private static HashSet<Integer> computeMaximalIndependetSet(HashMap<Integer, ArrayList<Integer>> mapOtoU, HashMap<Integer, ArrayList<Integer>> mapUtoO, HashMap<Integer, Integer> matching, HashMap<Integer, Integer> revMatching) { // use Koenig's theorem to find vertex cover // 2. add all vertices not contained in matching from O to T HashSet<Integer> T = new HashSet<Integer>(mapOtoU.keySet()); T.removeAll(matching.keySet()); // // -- debug // System.out.println("Initial T"); // for (Integer val : T) { // System.out.print((val+1) + ", "); // } // System.out.println(); // System.out.println("------"); // recently added vertices HashSet<Integer> recentlyAdded = new HashSet<Integer>(T); HashSet<Integer> recentlyAddedTmp = new HashSet<Integer>(); // true if we still need to check for more vertices boolean verticesAdded; // repeat until no more new vertices are found do { verticesAdded = false; // 3. we now move from recently added vertices to vertices in U on all not in maximum matching contained edges for (Integer vertex : recentlyAdded) { ArrayList<Integer> mapsTo = mapOtoU.get(vertex); Integer mapsToInMaxMatching = matching.get(vertex); for (Integer target : mapsTo) { // if this matching is not contained in maximum matching if (!target.equals(mapsToInMaxMatching)) { if (T.add(target)) { verticesAdded = true; } T.add(target); recentlyAddedTmp.add(target); } } } recentlyAdded.clear(); recentlyAdded.addAll(recentlyAddedTmp); recentlyAddedTmp.clear(); // check if we need to continue if (!verticesAdded) { break; } verticesAdded = false; // 4. we now move from recently added vertices to vertices in O on all in maximum matching contained edges for (Integer vertex : recentlyAdded) { Integer mapsToInMaxMatching = revMatching.get(vertex); if (mapsToInMaxMatching != null) { if (T.add(mapsToInMaxMatching)) { verticesAdded = true; } recentlyAddedTmp.add(mapsToInMaxMatching); } } recentlyAdded.clear(); recentlyAdded.addAll(recentlyAddedTmp); recentlyAddedTmp.clear(); } while (verticesAdded); // // -- debug // System.out.println("Final T"); // for (Integer val : T) { // System.out.print((val+1) + ", "); // } // System.out.println(); // System.out.println("------"); // compute the minimal vertex cover HashSet<Integer> minVertexCover = new HashSet<Integer>(mapOtoU.keySet()); minVertexCover.removeAll(T); HashSet<Integer> tmp = new HashSet<Integer>(mapUtoO.keySet()); tmp.retainAll(T); minVertexCover.addAll(tmp); // // -- debug // System.out.println("Minimal Vertex Cover"); // for (Integer vertex : minVertexCover) { // System.out.print((vertex+1) + ", "); // } // System.out.println(); // System.out.println("------"); // compute the maximal independent set HashSet<Integer> maxIndependentSet = new HashSet<Integer>(mapOtoU.keySet()); maxIndependentSet.addAll(mapUtoO.keySet()); maxIndependentSet.removeAll(minVertexCover); return maxIndependentSet; } }