package mindustry.input; import arc.*; import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.pooling.*; import mindustry.world.*; import static mindustry.Vars.*; public class Placement{ private static final NormalizeResult result = new NormalizeResult(); private static final NormalizeDrawResult drawResult = new NormalizeDrawResult(); private static Bresenham2 bres = new Bresenham2(); private static Seq points = new Seq<>(); //for pathfinding private static IntFloatMap costs = new IntFloatMap(); private static IntIntMap parents = new IntIntMap(); private static IntSet closed = new IntSet(); /** Normalize a diagonal line into points. */ public static Seq pathfindLine(boolean conveyors, int startX, int startY, int endX, int endY){ Pools.freeAll(points); points.clear(); if(conveyors && Core.settings.getBool("conveyorpathfinding")){ if(astar(startX, startY, endX, endY)){ return points; }else{ return normalizeLine(startX, startY, endX, endY); } }else{ return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points); } } /** Normalize two points into one straight line, no diagonals. */ public static Seq normalizeLine(int startX, int startY, int endX, int endY){ Pools.freeAll(points); points.clear(); if(Math.abs(startX - endX) > Math.abs(startY - endY)){ //go width for(int i = 0; i <= Math.abs(startX - endX); i++){ points.add(Pools.obtain(Point2.class, Point2::new).set(startX + i * Mathf.sign(endX - startX), startY)); } }else{ //go height for(int i = 0; i <= Math.abs(startY - endY); i++){ points.add(Pools.obtain(Point2.class, Point2::new).set(startX, startY + i * Mathf.sign(endY - startY))); } } return points; } private static float tileHeuristic(Tile tile, Tile other){ Block block = control.input.block; if((!other.block().alwaysReplace && !(block != null && block.canReplace(other.block()))) || other.floor().isDeep()){ return 20; }else{ if(parents.containsKey(tile.pos())){ Tile prev = world.tile(parents.get(tile.pos(), 0)); if(tile.relativeTo(prev) != other.relativeTo(tile)){ return 8; } } } return 1; } private static float distanceHeuristic(int x1, int y1, int x2, int y2){ return Math.abs(x1 - x2) + Math.abs(y1 - y2); } private static boolean validNode(Tile tile, Tile other){ Block block = control.input.block; if(block != null && block.canReplace(other.block())){ return true; }else{ return other.block().alwaysReplace; } } private static boolean astar(int startX, int startY, int endX, int endY){ Tile start = world.tile(startX, startY); Tile end = world.tile(endX, endY); if(start == end || start == null || end == null) return false; costs.clear(); closed.clear(); parents.clear(); int nodeLimit = 1000; int totalNodes = 0; PQueue queue = new PQueue<>(10, (a, b) -> Float.compare(costs.get(a.pos(), 0f) + distanceHeuristic(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + distanceHeuristic(b.x, b.y, end.x, end.y))); queue.add(start); boolean found = false; while(!queue.empty() && totalNodes++ < nodeLimit){ Tile next = queue.poll(); float baseCost = costs.get(next.pos(), 0f); if(next == end){ found = true; break; } closed.add(Point2.pack(next.x, next.y)); for(Point2 point : Geometry.d4){ int newx = next.x + point.x, newy = next.y + point.y; Tile child = world.tile(newx, newy); if(child != null && validNode(next, child)){ if(closed.add(child.pos())){ parents.put(child.pos(), next.pos()); costs.put(child.pos(), tileHeuristic(next, child) + baseCost); queue.add(child); } } } } if(!found) return false; int total = 0; points.add(Pools.obtain(Point2.class, Point2::new).set(endX, endY)); Tile current = end; while(current != start && total++ < nodeLimit){ if(current == null) return false; int newPos = parents.get(current.pos(), -1); if(newPos == -1) return false; points.add(Pools.obtain(Point2.class, Point2::new).set(Point2.x(newPos), Point2.y(newPos))); current = world.tile(newPos); } points.reverse(); return true; } /** * Normalizes a placement area and returns the result, ready to be used for drawing a rectangle. * Returned x2 and y2 will always be greater than x and y. * @param block block that will be drawn * @param startx starting X coordinate * @param starty starting Y coordinate * @param endx ending X coordinate * @param endy ending Y coordinate * @param snap whether to snap to a line * @param maxLength maximum length of area */ public static NormalizeDrawResult normalizeDrawArea(Block block, int startx, int starty, int endx, int endy, boolean snap, int maxLength, float scaling){ normalizeArea(startx, starty, endx, endy, 0, snap, maxLength); float offset = block.offset; drawResult.x = result.x * tilesize; drawResult.y = result.y * tilesize; drawResult.x2 = result.x2 * tilesize; drawResult.y2 = result.y2 * tilesize; drawResult.x -= block.size * scaling * tilesize / 2; drawResult.x2 += block.size * scaling * tilesize / 2; drawResult.y -= block.size * scaling * tilesize / 2; drawResult.y2 += block.size * scaling * tilesize / 2; drawResult.x += offset; drawResult.y += offset; drawResult.x2 += offset; drawResult.y2 += offset; return drawResult; } /** * Normalizes a placement area and returns the result. * Returned x2 and y2 will always be greater than x and y. * @param tilex starting X coordinate * @param tiley starting Y coordinate * @param endx ending X coordinate * @param endy ending Y coordinate * @param snap whether to snap to a line * @param rotation placement rotation * @param maxLength maximum length of area */ public static NormalizeResult normalizeArea(int tilex, int tiley, int endx, int endy, int rotation, boolean snap, int maxLength){ if(snap){ if(Math.abs(tilex - endx) > Math.abs(tiley - endy)){ endy = tiley; }else{ endx = tilex; } } if(Math.abs(endx - tilex) > maxLength){ endx = Mathf.sign(endx - tilex) * maxLength + tilex; } if(Math.abs(endy - tiley) > maxLength){ endy = Mathf.sign(endy - tiley) * maxLength + tiley; } int dx = endx - tilex, dy = endy - tiley; if(Math.abs(dx) > Math.abs(dy)){ if(dx >= 0){ rotation = 0; }else{ rotation = 2; } }else if(Math.abs(dx) < Math.abs(dy)){ if(dy >= 0){ rotation = 1; }else{ rotation = 3; } } if(endx < tilex){ int t = endx; endx = tilex; tilex = t; } if(endy < tiley){ int t = endy; endy = tiley; tiley = t; } result.x2 = endx; result.y2 = endy; result.x = tilex; result.y = tiley; result.rotation = rotation; return result; } public static class NormalizeDrawResult{ float x, y, x2, y2; } public static class NormalizeResult{ public int x, y, x2, y2, rotation; boolean isX(){ return Math.abs(x2 - x) > Math.abs(y2 - y); } /** * Returns length of greater edge of the selection. */ int getLength(){ return Math.max(x2 - x, y2 - y); } /** * Returns the X position of a specific index along this area as a line. */ int getScaledX(int i){ return x + (x2 - x > y2 - y ? i : 0); } /** * Returns the Y position of a specific index along this area as a line. */ int getScaledY(int i){ return y + (x2 - x > y2 - y ? 0 : i); } } }