package algorithms.rotationalplanesweep; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.awt.Color; import draw.GridLineSet; import draw.GridPointSet; import algorithms.datatypes.SnapshotItem; import grid.GridGraph; public class RPSScanner { public static ArrayList<List<SnapshotItem>> snapshotList = new ArrayList<>(); private final void saveSnapshot(int sx, int sy, Vertex v) { ArrayList<SnapshotItem> snapshot = new ArrayList<>(); snapshot.add(SnapshotItem.generate(new Integer[]{sx, sy, v.x, v.y}, Color.RED)); // Snapshot current state of heap int heapSize = edgeHeap.size(); Edge[] edges = edgeHeap.getEdgeList(); for (int k=0; k<heapSize; ++k) { Color colour = (k == 0) ? Color.CYAN : Color.GREEN; Edge e = edges[k]; snapshot.add(SnapshotItem.generate(new Integer[]{e.u.x, e.u.y, e.v.x, e.v.y}, colour)); } snapshotList.add(new ArrayList<SnapshotItem>(snapshot)); } public static final void clearSnapshots() { snapshotList.clear(); } public int nSuccessors; public int[] successorsX; public int[] successorsY; private final Vertex[] vertices; private final RPSEdgeHeap edgeHeap; private final GridGraph graph; public static class Vertex { public int x; public int y; public double angle; public Edge edge1; // edge1 goes forward public Edge edge2; // edge2 goes backward public Vertex(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return x + ", " + y; } } public static class Edge { private static final double EPSILON = 0.00000001; public Vertex u; // u goes backward public Vertex v; // v goes forward public int heapIndex; //public double distance; public Edge(RPSScanner.Vertex u, RPSScanner.Vertex v) { this.u = u; this.v = v; } // Condition: e2 comes after e1 in rotational plane sweep order. private static final boolean isLessThan(Edge e1, Edge e2, int sx, int sy) { // compare u and e.v if (e1.u.x == e2.v.x && e1.u.y == e2.v.y) { // have to do flipping double m1x = e1.midX(); double m1y = e1.midY(); double e1_distance_sx_sy_m1x_m1y = e1.distance(sx, sy, m1x, m1y); double diff1 = e1_distance_sx_sy_m1x_m1y - e2.distance(sx, sy, m1x, m1y); double m2x = e2.midX(); double m2y = e2.midY(); double diff2 = e1.distance(sx, sy, m2x, m2y) - e2.distance(sx, sy, m2x, m2y); if (diff1*diff2 < 0) { // opposite signs // need to flip m1 to the opposite side. // u1 == v2 int du1x = e1.u.x - sx; int du1y = e1.u.y - sy; double dm1x = m1x - sx; double dm1y = m1y - sy; // m1r is the reflected midpoint about the line s,u1 // m1r = m1 - 2*(dm1 - <dm1,du1>/<du1,du1> du1) double dotprodRatio = (dm1x*du1x + dm1y*du1y) / (du1x*du1x + du1y*du1y); double m1rx = m1x - 2*(dm1x - dotprodRatio*du1x); double m1ry = m1y - 2*(dm1y - dotprodRatio*du1y); return e1_distance_sx_sy_m1x_m1y < e2.distance(sx, sy, m1rx, m1ry); } return diff1 < 0; } else { // Find a ray (sx,sy)->(tx,ty) that crosses both line segments int du1x = e1.u.x - sx; int du1y = e1.u.y - sy; int du2x = e2.u.x - sx; int du2y = e2.u.y - sy; int crossProd = du1x*du2y - du1y*du2x; double tx, ty; if (crossProd >= 0) { tx = (double)(e1.u.x + e2.v.x)/2; ty = (double)(e1.u.y + e2.v.y)/2; } else { tx = (double)(e2.u.x + e2.v.x)/2; ty = (double)(e2.u.y + e2.v.y)/2; } //System.out.println("Ray -> " + tx + ", " + ty); return e1.distance(sx, sy, tx, ty) < e2.distance(sx, sy, tx, ty); } } public final boolean isLessThan(Edge e, int sx, int sy) { if ((u.x == sx && u.y == sy) || (v.x == sx && v.y == sy)) return true; if ((e.u.x == sx && e.u.y == sy) || (e.v.x == sx && e.v.y == sy)) return false; int dv1x = v.x - sx; int dv1y = v.y - sy; int dv2x = e.v.x - sx; int dv2y = e.v.y - sy; int crossProd = dv1x*dv2y - dv1y*dv2x; if (crossProd >= 0) { // this -> e return isLessThan(this, e, sx, sy); } else { // e -> this return !isLessThan(e, this, sx, sy); } } private final double midX() { return (double)(u.x + v.x)/2; } private final double midY() { return (double)(u.y + v.y)/2; } private final double distance(int sx, int sy, double tx, double ty) { double dx = tx - sx; double dy = ty - sy; int dex = v.x - u.x; int dey = v.y - u.y; int dux = u.x - sx; int duy = u.y - sy; double denom = dey*dx - dex*dy; if (Math.abs(denom) < EPSILON) { // collinear (degenerate case) int dvx = v.x - sx; int dvy = v.y - sy; // (sx,sy) lies between u and v if (dux*dvx + duy*dvy <= 0) return 0; return Math.min(dux*dux + duy*duy, dvx*dvx + dvy*dvy); } else { // not collinear int numer = dux*dey - duy*dex; return (dx*dx+dy*dy)*numer*numer/(denom*denom); } } @Override public String toString() { return u.x + ", " + u.y + ", " + v.x + ", " + v.y; } } public RPSScanner(Vertex[] vertices, Edge[] edges, GridGraph graph) { successorsX = new int[11]; successorsY = new int[11]; nSuccessors = 0; this.vertices = vertices; this.edgeHeap = new RPSEdgeHeap(edges); this.graph = graph; } private final void clearNeighbours() { nSuccessors = 0; } private final void addNeighbour(int x, int y) { if (nSuccessors >= successorsX.length) { successorsX = Arrays.copyOf(successorsX, successorsX.length*2); successorsY = Arrays.copyOf(successorsY, successorsY.length*2); } successorsX[nSuccessors] = x; successorsY[nSuccessors] = y; ++nSuccessors; } private final void initialiseScan(int sx, int sy) { // Compute angles for (int i=0; i<vertices.length; ++i) { Vertex v = vertices[i]; if (v.x != sx || v.y != sy) { v.angle = Math.atan2(v.y-sy, v.x-sx); if (v.angle < 0) v.angle += 2*Math.PI; } else { v.angle = -1; Vertex n1 = v.edge1.v; Vertex n2 = v.edge2.u; if (graph.isOuterCorner(n1.x, n1.y)) addNeighbour(n1.x, n1.y); if (graph.isOuterCorner(n2.x, n2.y)) addNeighbour(n2.x, n2.y); } } sortVertices(sx, sy); edgeHeap.clear(); Edge[] edges = edgeHeap.getEdgeList(); // Note: iterating through the edges like this is a very dangerous operation. // That's because the edges array changes as you insert the edges into the heap. // Reason why it works: When we swap two edges when inserting, both edges have already been checked. // That's because we only swap with edges in the heap, which have a lower index than i. for (int i=0; i<edges.length; ++i) { Edge edge = edges[i]; if (intersectsPositiveXAxis(sx, sy, edge)) { edgeHeap.insert(edge, sx, sy); } } } public final void computeAllVisibleSuccessors(int sx, int sy) { clearNeighbours(); if (vertices.length == 0) return; if (!graph.isUnblockedCoordinate(sx, sy)) return; initialiseScan(sx, sy); // This queue is used to enforce the order: // INSERT TO EDGEHEAP -> ADD AS NEIGHBOUR -> DELETE FROM EDGEHEAP // for all vertices with the same angle from (sx,sy). Vertex[] vertexQueue = new Vertex[11]; int vertexQueueSize = 0; int i = 0; // Skip vertex if it is (sx,sy). while (vertices[i].x == sx && vertices[i].y == sy) ++i; for (; i<vertices.length; ++i) { if (vertexQueueSize >= vertexQueue.length) { vertexQueue = Arrays.copyOf(vertexQueue, vertexQueue.length*2); } vertexQueue[vertexQueueSize++] = vertices[i]; if (i+1 == vertices.length || !isSameAngle(sx, sy, vertices[i], vertices[i+1])) { // Clear queue // Insert all first for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeAddEdge(sx, sy, v, v.edge1); maybeAddEdge(sx, sy, v, v.edge2); } // Add all for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; //saveSnapshot(sx, sy, v); // UNCOMMENT FOR TRACING Edge edge = edgeHeap.getMin(); if (!linesIntersect(sx, sy, v.x, v.y, edge.u.x, edge.u.y, edge.v.x, edge.v.y)) { if (graph.isOuterCorner(v.x, v.y)) { addNeighbour(v.x, v.y); } } } // Delete all for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeDeleteEdge(sx, sy, v, v.edge1); maybeDeleteEdge(sx, sy, v, v.edge2); } // Clear queue vertexQueueSize = 0; } } } public final void computeAllVisibleTautSuccessors(int sx, int sy) { clearNeighbours(); if (vertices.length == 0) return; if (!graph.isUnblockedCoordinate(sx, sy)) return; initialiseScan(sx, sy); // This queue is used to enforce the order: // INSERT TO EDGEHEAP -> ADD AS NEIGHBOUR -> DELETE FROM EDGEHEAP // for all vertices with the same angle from (sx,sy). Vertex[] vertexQueue = new Vertex[11]; int vertexQueueSize = 0; int i = 0; // Skip vertex if it is (sx,sy). while (vertices[i].x == sx && vertices[i].y == sy) ++i; for (; i<vertices.length; ++i) { if (vertexQueueSize >= vertexQueue.length) { vertexQueue = Arrays.copyOf(vertexQueue, vertexQueue.length*2); } vertexQueue[vertexQueueSize++] = vertices[i]; if (i+1 == vertices.length || !isSameAngle(sx, sy, vertices[i], vertices[i+1])) { // Clear queue // Insert all first for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeAddEdge(sx, sy, v, v.edge1); maybeAddEdge(sx, sy, v, v.edge2); } // Add all for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; if (!isTautSuccessor(sx, sy, v.x, v.y)) continue; //saveSnapshot(sx, sy, v); // UNCOMMENT FOR TRACING Edge edge = edgeHeap.getMin(); if (!linesIntersect(sx, sy, v.x, v.y, edge.u.x, edge.u.y, edge.v.x, edge.v.y)) { addNeighbour(v.x, v.y); } } // Delete all for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeDeleteEdge(sx, sy, v, v.edge1); maybeDeleteEdge(sx, sy, v, v.edge2); } // Clear queue vertexQueueSize = 0; } } } public final void computeAllVisibleTwoWayTautSuccessors(int sx, int sy) { clearNeighbours(); if (vertices.length == 0) return; if (!graph.isUnblockedCoordinate(sx, sy)) return; initialiseScan(sx, sy); // We exclude the non-taut region (excludeStart, excludeEnd) double EPSILON = 0.00000001; double excludeStart = 99999; double excludeEnd = 99998; // Setting the interval (excludeStart, excludeEnd) if (graph.bottomLeftOfBlockedTile(sx, sy)) { if (!graph.topRightOfBlockedTile(sx, sy)) { excludeStart = Math.PI + EPSILON; excludeEnd = 3*Math.PI/2 - EPSILON; } } else if (graph.bottomRightOfBlockedTile(sx, sy)) { if (!graph.topLeftOfBlockedTile(sx, sy)) { excludeStart = 3*Math.PI/2 + EPSILON; excludeEnd = 2*Math.PI - EPSILON; } } else if (graph.topRightOfBlockedTile(sx, sy)) { excludeStart = 0 + EPSILON; excludeEnd = Math.PI/2 - EPSILON; } else if (graph.topLeftOfBlockedTile(sx, sy)) { excludeStart = Math.PI/2 + EPSILON; excludeEnd = Math.PI - EPSILON; } // This queue is used to enforce the order: // INSERT TO EDGEHEAP -> ADD AS NEIGHBOUR -> DELETE FROM EDGEHEAP // for all vertices with the same angle from (sx,sy). Vertex[] vertexQueue = new Vertex[11]; int vertexQueueSize = 0; int i = 0; // Skip vertex if it is (sx,sy). while (vertices[i].x == sx && vertices[i].y == sy) ++i; for (; i<vertices.length; ++i) { if (vertexQueueSize >= vertexQueue.length) { vertexQueue = Arrays.copyOf(vertexQueue, vertexQueue.length*2); } vertexQueue[vertexQueueSize++] = vertices[i]; double currentAngle = vertices[i].angle; if (i+1 == vertices.length || !isSameAngle(sx, sy, vertices[i], vertices[i+1])) { // Clear queue // Insert all first for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeAddEdge(sx, sy, v, v.edge1); maybeAddEdge(sx, sy, v, v.edge2); } // Add all (if it doesn't fall within the interval (excludeStart, excludeEnd) ) if (currentAngle <= excludeStart || excludeEnd <= currentAngle) { for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; if (!isTautSuccessor(sx, sy, v.x, v.y)) continue; //saveSnapshot(sx, sy, v); // UNCOMMENT FOR TRACING Edge edge = edgeHeap.getMin(); if (!linesIntersect(sx, sy, v.x, v.y, edge.u.x, edge.u.y, edge.v.x, edge.v.y)) { addNeighbour(v.x, v.y); } } } // Delete all for (int j=0; j<vertexQueueSize; ++j) { Vertex v = vertexQueue[j]; maybeDeleteEdge(sx, sy, v, v.edge1); maybeDeleteEdge(sx, sy, v, v.edge2); } // Clear queue vertexQueueSize = 0; } } } // Assumptions: // 1. (sx, sy) != (nx, ny) // 2. (sx, sy) has line of sight to (nx, ny) // 3. (nx, ny) is an outer corner tile. private final boolean isTautSuccessor(int sx, int sy, int nx, int ny) { int dx = nx - sx; int dy = ny - sy; if (dx == 0 || dy == 0) return graph.isOuterCorner(nx, ny); if (dx > 0) { if (dy > 0) { return !graph.bottomLeftOfBlockedTile(nx, ny); } else { // (dy < 0) return !graph.topLeftOfBlockedTile(nx, ny); } } else { // (dx < 0) if (dy > 0) { return !graph.bottomRightOfBlockedTile(nx, ny); } else { // (dy < 0) return !graph.topRightOfBlockedTile(nx, ny); } } } private final void sortVertices(int sx, int sy) { Arrays.sort(vertices, (a,b) -> Double.compare(a.angle, b.angle)); } private final boolean isSameAngle(int sx, int sy, Vertex u, Vertex v) { int dx1 = u.x - sx; int dy1 = u.y - sy; int dx2 = v.x - sx; int dy2 = v.y - sy; return dx1*dx2 + dy1*dy2 > 0 && dx1*dy2 == dx2*dy1; } private final void maybeAddEdge(int sx, int sy, Vertex curr, Edge edge) { if (curr != edge.v) return; int dux = edge.u.x - sx; int duy = edge.u.y - sy; int dvx = edge.v.x - sx; int dvy = edge.v.y - sy; int crossProd = dux*dvy - dvx*duy; if (crossProd < 0) { // Add/delete edgeHeap.insert(edge, sx, sy); } else if (crossProd == 0) { int dotProd = dux*dvx + duy*dvy; if (dotProd > 0) { // Don't add } else if (dotProd < 0) { // Add/delete edgeHeap.insert(edge, sx, sy); } else { // dotProd == 0 // Add edge and neighbour edgeHeap.insert(edge, sx, sy); edgeHeap.insert(edge.u.edge2, sx, sy); } } } private final void maybeDeleteEdge(int sx, int sy, Vertex curr, Edge edge) { if (curr != edge.u) return; int dux = edge.u.x - sx; int duy = edge.u.y - sy; int dvx = edge.v.x - sx; int dvy = edge.v.y - sy; int crossProd = dux*dvy - dvx*duy; if (crossProd < 0) { // Add/delete edgeHeap.delete(edge, sx, sy); } else if (crossProd == 0) { int dotProd = dux*dvx + duy*dvy; if (dotProd > 0) { // Don't add } else if (dotProd < 0) { // Add/delete edgeHeap.delete(edge, sx, sy); } else { // dotProd == 0 // Delete edge and neighbour edgeHeap.delete(edge, sx, sy); edgeHeap.delete(edge.v.edge1, sx, sy); } } } private final boolean intersectsPositiveXAxis(int sx, int sy, Edge edge) { if (sx == edge.u.x && sy == edge.u.y) { return anglesIntersectPositiveXAxis(sx, sy, edge.u.edge2.u, edge.v); } else if (sx == edge.v.x && sy == edge.v.y) { return anglesIntersectPositiveXAxis(sx, sy, edge.u, edge.v.edge1.v); } else { return intersectsPositiveXAxis(sx, sy, edge.u, edge.v); } } private final boolean intersectsPositiveXAxis(int sx, int sy, Vertex edgeU, Vertex edgeV) { if (anglesIntersectPositiveXAxis(sx, sy, edgeU, edgeV)) { int dux = edgeU.x - sx; int duy = edgeU.y - sy; int dvx = edgeV.x - sx; int dvy = edgeV.y - sy; int crossProd = dux*dvy - dvx*duy; if (crossProd < 0) { return true; } else if (crossProd == 0) { int dotProd = dux*dvx + duy*dvy; if (dotProd > 0) { // Don't add } else if (dotProd < 0) { return true; } else { // (dotProd == 0) // Never happens. throw new UnsupportedOperationException("This should not happen"); } } } return false; } private final boolean anglesIntersectPositiveXAxis(int sx, int sy, Vertex edgeU, Vertex edgeV) { return edgeU.angle <= edgeV.angle && !isSameAngle(sx, sy, edgeU, edgeV); } private final boolean linesIntersect(int sx, int sy, int tx, int ty, int ux, int uy, int vx, int vy) { int line1dx = tx - sx; int line1dy = ty - sy; int cross1 = (ux-sx)*line1dy - (uy-sy)*line1dx; int cross2 = (vx-sx)*line1dy - (vy-sy)*line1dx; int line2dx = vx - ux; int line2dy = vy - uy; int cross3 = (sx-ux)*line2dy - (sy-uy)*line2dx; int cross4 = (tx-ux)*line2dy - (ty-uy)*line2dx; if (cross1 != 0 && cross2 != 0 && cross3 != 0 && cross4 != 0) { return ((cross1 > 0) != (cross2 > 0)) && ((cross3 > 0) != (cross4 > 0)); } // There exists a cross product that is 0. One of the degenerate cases. // Not possible: (sx == ux && sy == uy) or (sx == vx && sy == vy) if (tx == ux && ty == uy) { if (sx == vx && sy == vy) return true; int dx1 = sx-tx; int dy1 = sy-ty; int dx2 = vx-tx; int dy2 = vy-ty; int dx3 = sx-vx; int dy3 = sy-vy; return (dx1*dx2 + dy1*dy2 > 0) && (dx1*dx3 + dy1*dy3 > 0) && (dx1*dy2 == dx2*dy1); } else if (tx == vx && ty == vy) { if (sx == ux && sy == uy) return true; int dx1 = sx-tx; int dy1 = sy-ty; int dx2 = ux-tx; int dy2 = uy-ty; int dx3 = sx-ux; int dy3 = sy-uy; return (dx1*dx2 + dy1*dy2 > 0) && (dx1*dx3 + dy1*dy3 > 0) && (dx1*dy2 == dx2*dy1); } else { // No equalities whatsoever. // We consider this case an intersection if they intersect. int prod1 = cross1*cross2; int prod2 = cross3*cross4; if (prod1 == 0 && prod2 == 0) { // All four points collinear. int minX1; int minY1; int maxX1; int maxY1; int minX2; int minY2; int maxX2; int maxY2; if (sx < tx) {minX1 = sx; maxX1 = tx;} else {minX1 = tx; maxX1 = sx;} if (sy < ty) {minY1 = sy; maxY1 = ty;} else {minY1 = ty; maxY1 = sy;} if (ux < vx) {minX2 = ux; maxX2 = vx;} else {minX2 = vx; maxX2 = ux;} if (uy < vy) {minY2 = uy; maxY2 = vy;} else {minY2 = vy; maxY2 = uy;} return !(maxX1 < minX2 || maxY1 < minY2 || maxX2 < minX1 || maxY2 < minY1); } return (prod1 <= 0 && prod2 <= 0); } } public void drawLines(GridLineSet gridLineSet, GridPointSet gridPointSet) { for (int i=0; i<vertices.length; ++i) { Vertex v = vertices[i]; gridPointSet.addPoint(v.x, v.y, Color.YELLOW); } Edge[] edges = edgeHeap.getEdgeList(); for (int i=0; i<edges.length; ++i) { Edge e = edges[i]; gridLineSet.addLine(e.u.x, e.u.y, e.v.x, e.v.y, Color.RED); } } public void snapshotHeap(GridLineSet gridLineSet) { Edge[] edges = edgeHeap.getEdgeList(); for (int i=0; i<edgeHeap.size(); ++i) { Color colour = (i == 0) ? Color.ORANGE : Color.RED; Edge e = edges[i]; gridLineSet.addLine(e.u.x, e.u.y, e.v.x, e.v.y, colour); } } }