From 95d61fc4d700b7bc3d9450ddba9558b556aed367 Mon Sep 17 00:00:00 2001 From: Collin Smith Date: Sun, 17 Feb 2019 18:50:14 -0800 Subject: [PATCH] Added extremely basic path-finding algorithm Added extremely basic path-finding algorithm Added accessor to Zone.flags --- build.gradle | 1 + core/src/gdx/diablo/map/Map.java | 16 ++ core/src/gdx/diablo/map/MapRenderer.java | 106 ++++++++++ core/src/gdx/diablo/map/MapUtils.java | 192 +++++++++++++++++++ mapbuilder/src/gdx/diablo/map/MapViewer.java | 65 +++++++ 5 files changed, 380 insertions(+) create mode 100644 core/src/gdx/diablo/map/MapUtils.java diff --git a/build.gradle b/build.gradle index 7e76fcb9..5baa7ffe 100644 --- a/build.gradle +++ b/build.gradle @@ -164,6 +164,7 @@ project(":core") { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" + compile "com.badlogicgames.gdx:gdx-ai:1.8.+" } dependencies { diff --git a/core/src/gdx/diablo/map/Map.java b/core/src/gdx/diablo/map/Map.java index a91872bd..960094f4 100644 --- a/core/src/gdx/diablo/map/Map.java +++ b/core/src/gdx/diablo/map/Map.java @@ -3,11 +3,14 @@ package gdx.diablo.map; import com.google.common.base.Preconditions; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ai.pfa.DefaultGraphPath; +import com.badlogic.gdx.ai.pfa.GraphPath; import com.badlogic.gdx.assets.AssetDescriptor; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Bits; import com.badlogic.gdx.utils.Disposable; @@ -543,6 +546,11 @@ public class Map implements Disposable { } } + public GraphPath path(Vector3 src, Vector3 dst) { + //return new MapGraph(this).path(src, dst); + return MapUtils.path(this, src, dst, new DefaultGraphPath()); + } + static class Zone { static final Array EMPTY_ARRAY = new Array<>(0); @@ -662,6 +670,14 @@ public class Map implements Disposable { return presets[(tx - this.tx) / gridSizeX][(ty - this.ty) / gridSizeY]; } + public int flags(int x, int y) { + x -= this.x; + if (x < 0 || x > width ) return 0xFF; + y -= this.y; + if (y < 0 || y > height) return 0xFF; + return flags[x][y] & 0xFF; + } + void load(DT1s dt1s) { Preconditions.checkState(tiles == null, "tiles have already been loaded"); tiles = new Tile[Map.MAX_LAYERS][][]; diff --git a/core/src/gdx/diablo/map/MapRenderer.java b/core/src/gdx/diablo/map/MapRenderer.java index ee5b99e8..44c7be49 100644 --- a/core/src/gdx/diablo/map/MapRenderer.java +++ b/core/src/gdx/diablo/map/MapRenderer.java @@ -1,6 +1,7 @@ package gdx.diablo.map; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ai.pfa.GraphPath; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.BitmapFont; @@ -137,6 +138,22 @@ public class MapRenderer { return null; } + public Vector3 getCursor() { + Vector3 coords = new Vector3(); + coords.set(Gdx.input.getX(), Gdx.input.getY(), 0); + camera.unproject(coords); + float adjustX = (int) coords.x; + float adjustY = (int) coords.y - Tile.SUBTILE_HEIGHT50; + + float selectX = ( adjustX / Tile.SUBTILE_WIDTH50 - adjustY / Tile.SUBTILE_HEIGHT50) / 2; + float selectY = (-adjustX / Tile.SUBTILE_WIDTH50 - adjustY / Tile.SUBTILE_HEIGHT50) / 2; + if (selectX < 0) selectX--; + if (selectY < 0) selectY--; + coords.x = (int) selectX; + coords.y = (int) selectY; + return coords; + } + public void setMap(Map map) { if (this.map != map) { this.map = map; @@ -852,6 +869,88 @@ public class MapRenderer { shapes.set(ShapeRenderer.ShapeType.Line); } + public void renderDebugPath(ShapeRenderer shapes, Vector3 src, Vector3 dst) { + shapes.setColor(Color.TAN); + shapes.set(ShapeRenderer.ShapeType.Filled); + if (Math.abs(dst.y - src.y) < Math.abs(dst.x - src.x)) { + if (src.x > dst.x) { + plotLineLow(shapes, dst, src); + } else { + plotLineLow(shapes, src, dst); + } + } else { + if (src.y > dst.y) { + plotLineHigh(shapes, dst, src); + } else { + plotLineHigh(shapes, src, dst); + } + } + + shapes.set(ShapeRenderer.ShapeType.Line); + } + + private void plotLineLow(ShapeRenderer shapes, Vector3 src, Vector3 dst) { + float dx = dst.x - src.x; + float dy = dst.y - src.y; + int yi = 1; + if (dy < 0) { + yi = -1; + dy = -dy; + } + + float D = 2*dy - dx; + float y = src.y; + for (float x = src.x; x <= dst.x; x++) { + float px = +((int) x * Tile.SUBTILE_WIDTH50) - ((int) y * Tile.SUBTILE_WIDTH50) - Tile.SUBTILE_WIDTH50; + float py = -((int) x * Tile.SUBTILE_HEIGHT50) - ((int) y * Tile.SUBTILE_HEIGHT50) - Tile.SUBTILE_HEIGHT50; + drawDiamondSolid(shapes, px, py, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); + if (D > 0) { + y = y + yi; + D = D - 2*dx; + } + + D = D + 2*dy; + } + } + + private void plotLineHigh(ShapeRenderer shapes, Vector3 src, Vector3 dst) { + float dx = dst.x - src.x; + float dy = dst.y - src.y; + int xi = 1; + if (dx < 0) { + xi = -1; + dx = -dx; + } + + float D = 2*dx - dy; + float x = src.x; + for (float y = src.y; y <= dst.y; y++) { + float px = +((int) x * Tile.SUBTILE_WIDTH50) - ((int) y * Tile.SUBTILE_WIDTH50) - Tile.SUBTILE_WIDTH50; + float py = -((int) x * Tile.SUBTILE_HEIGHT50) - ((int) y * Tile.SUBTILE_HEIGHT50) - Tile.SUBTILE_HEIGHT50; + drawDiamondSolid(shapes, px, py, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); + if (D > 0) { + x = x + xi; + D = D - 2*dy; + } + + D = D + 2*dx; + } + } + + public void renderDebugPath2(ShapeRenderer shapes, GraphPath path) { + shapes.setColor(Color.TAN); + shapes.set(ShapeRenderer.ShapeType.Filled); + final int size = path.getCount(); + for (int i = 0; i < size; i++) { + MapUtils.Point2 point = path.get(i); + float px = +(point.x * Tile.SUBTILE_WIDTH50) - (point.y * Tile.SUBTILE_WIDTH50) - Tile.SUBTILE_WIDTH50; + float py = -(point.x * Tile.SUBTILE_HEIGHT50) - (point.y * Tile.SUBTILE_HEIGHT50) - Tile.SUBTILE_HEIGHT50; + drawDiamondSolid(shapes, px, py, Tile.SUBTILE_WIDTH, Tile.SUBTILE_HEIGHT); + } + + shapes.set(ShapeRenderer.ShapeType.Line); + } + private static void drawDiamond(ShapeRenderer shapes, float x, float y, int width, int height) { int hw = width >>> 1; int hh = height >>> 1; @@ -860,4 +959,11 @@ public class MapRenderer { shapes.line(x + width, y + hh , x + hw , y ); shapes.line(x + hw , y , x , y + hh ); } + + private static void drawDiamondSolid(ShapeRenderer shapes, float x, float y, int width, int height) { + int hw = width >>> 1; + int hh = height >>> 1; + shapes.triangle(x, y + hh, x + hw, y + height, x + width, y + hh); + shapes.triangle(x, y + hh, x + hw, y , x + width, y + hh); + } } diff --git a/core/src/gdx/diablo/map/MapUtils.java b/core/src/gdx/diablo/map/MapUtils.java new file mode 100644 index 00000000..c9f02a99 --- /dev/null +++ b/core/src/gdx/diablo/map/MapUtils.java @@ -0,0 +1,192 @@ +package gdx.diablo.map; + +import com.badlogic.gdx.ai.pfa.GraphPath; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.Array; + +public class MapUtils { + + private MapUtils() {} + + public static GraphPath path(Map map, Vector3 src, Vector3 dst, GraphPath path) { + Point2 srcP = new Point2(src); + Point2 dstP = new Point2(dst); + Array coords = new Array<>(); + Array children = new Array<>(8); + coords.add(dstP); + boolean found = false; + do { + for (Point2 point : coords) { + found = expand(map, srcP, point, coords, children); + coords.addAll(children); + if (found) break; + } + } while (!found); + + if (!found) { + return path; + } + + Point2 next = null; + Point2 last = coords.removeIndex(coords.size - 1); + while (true) { + for (Point2 coord : coords) { + if (last.adjacent(coord)) { + if (next == null || coord.cost < next.cost) { + next = coord; + } + } + } + + path.add(next); + last = next; + next = null; + if (dstP.x == last.x && dstP.y == last.y) break; + } + + return path; + } + + private static boolean expand(Map map, Point2 dst, Point2 src, Array coords, Array children) { + children.clear(); + if (check(map, dst, src, src.x - 1, src.y - 1, coords, children)) return true; + if (check(map, dst, src, src.x - 1, src.y , coords, children)) return true; + if (check(map, dst, src, src.x - 1, src.y + 1, coords, children)) return true; + if (check(map, dst, src, src.x , src.y - 1, coords, children)) return true; + if (check(map, dst, src, src.x , src.y , coords, children)) return true; + if (check(map, dst, src, src.x , src.y + 1, coords, children)) return true; + if (check(map, dst, src, src.x + 1, src.y - 1, coords, children)) return true; + if (check(map, dst, src, src.x + 1, src.y , coords, children)) return true; + if (check(map, dst, src, src.x + 1, src.y + 1, coords, children)) return true; + return false; + } + + private static boolean check(Map map, Point2 dst, Point2 src, int x, int y, Array coords, Array children) { + Map.Zone zone = map.getZone(x, y); + if (zone != null) { + if (zone.flags(x, y) == 0) { + float cost = src.cost; + cost += (x != src.x && y != src.y) ? 1.414213562373095f : 1; + if (!contains(coords, x, y, cost)) { + Point2 point = new Point2(x, y, cost); + children.add(point); + if (dst.x == x && dst.y == y) return true; + } + } + } + + return false; + } + + private static boolean contains(Array coords, int x, int y, float cost) { + for (Point2 point : new Array.ArrayIterator<>(coords)) { + if (point.x == x && point.y == y && point.cost <= cost) { + return true; + } + } + + return false; + } + + static class Point2 { + int x; + int y; + float cost; + + Point2(int x, int y, float cost) { + this.x = x; + this.y = y; + this.cost = cost; + } + + Point2(Vector3 src) { + x = (int) src.x; + y = (int) src.y; + } + + boolean adjacent(Point2 other) { + return Vector2.dst2(x, y, other.x, other.y) < 2; + } + } + + /* + public static Array path(Map map, Vector3 src, Vector3 dst) { + ObjectSet open = new ObjectSet<>(); + ObjectSet closed = new ObjectSet<>(); + open.add(src); + + int x, y; + while (!open.isEmpty()) { + Node cur = null; + if (cur.data.equals(dst)) { + return null; + } + + x = (int) cur.data.x; + y = (int) cur.data.y; + + Array neighbors = getNeighbors(map, cur, x, y); + for (Node neighbor : neighbors) { + float score = cur.cost + neighbor.cost; + } + } + + return null; + } + + private static Array getNeighbors(Map map, Node n, int x, int y) { + Array neighbors = new Array<>(8); + addIfValid(neighbors, map, n, x - 1, y - 1); + addIfValid(neighbors, map, n, x - 1, y ); + addIfValid(neighbors, map, n, x - 1, y + 1); + addIfValid(neighbors, map, n, x , y - 1); + addIfValid(neighbors, map, n, x , y ); + addIfValid(neighbors, map, n, x , y + 1); + addIfValid(neighbors, map, n, x + 1, y - 1); + addIfValid(neighbors, map, n, x + 1, y ); + addIfValid(neighbors, map, n, x + 1, y + 1); + return neighbors; + } + + private static boolean addIfValid(Array arr, Map map, Node n, int x, int y) { + Map.Zone zone = map.getZone(x, y); + if (zone != null) { + if (zone.flags(x, y) == 0) { + Node node = new Node(); + node.prev = n; + node.data = new Vector3(x, y, 0); + if (x != node.data.x && y != node.data.y) { + node.cost = 1.414213562373095f; + } else { + node.cost = 1; + } + + arr.add(node); + } + } + + return false; + } + + static class Node { + Node prev; + Vector3 data; + float cost; + float score; + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (obj == this) return true; + if (!(obj instanceof Node)) return false; + return data.equals(((Node) obj).data); + } + + @Override + public int hashCode() { + return data.hashCode(); + } + } + */ +} diff --git a/mapbuilder/src/gdx/diablo/map/MapViewer.java b/mapbuilder/src/gdx/diablo/map/MapViewer.java index 8c502c98..f2e3e622 100644 --- a/mapbuilder/src/gdx/diablo/map/MapViewer.java +++ b/mapbuilder/src/gdx/diablo/map/MapViewer.java @@ -6,9 +6,11 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputAdapter; import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.ai.pfa.GraphPath; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; @@ -16,6 +18,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.utils.UIUtils; import com.badlogic.gdx.utils.GdxRuntimeException; @@ -39,6 +42,7 @@ import gdx.diablo.loader.COFLoader; import gdx.diablo.loader.DC6Loader; import gdx.diablo.loader.DCCLoader; import gdx.diablo.loader.TXTLoader; +import gdx.diablo.map.DT1.Tile; import gdx.diablo.mpq.MPQFileHandleResolver; public class MapViewer extends ApplicationAdapter { @@ -70,6 +74,10 @@ public class MapViewer extends ApplicationAdapter { int x, y; + Vector3 src; + Vector3 dst; + GraphPath path; + boolean drawCrosshair; boolean drawGrid; boolean drawFlags; @@ -126,6 +134,50 @@ public class MapViewer extends ApplicationAdapter { mapRenderer.resize(); InputMultiplexer multiplexer = new InputMultiplexer(); + multiplexer.addProcessor(new InputAdapter() { + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + switch (button) { + case Input.Buttons.LEFT: + src = mapRenderer.getCursor(); + dst = null; + break; + case Input.Buttons.RIGHT: + src = dst = null; + break; + } + return true; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int button) { + if (src != null) { + dst = mapRenderer.getCursor(); + } + return true; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + switch (button) { + case Input.Buttons.LEFT: + dst = mapRenderer.getCursor(); + System.out.println("src = " + src); + System.out.println("dst = " + dst); + + //float srcX = +(src.x * Tile.SUBTILE_WIDTH50) - (src.y * Tile.SUBTILE_WIDTH50); + //float srcY = -(src.x * Tile.SUBTILE_HEIGHT50) - (src.y * Tile.SUBTILE_HEIGHT50); + //float dstX = +(dst.x * Tile.SUBTILE_WIDTH50) - (dst.y * Tile.SUBTILE_WIDTH50); + //float dstY = -(dst.x * Tile.SUBTILE_HEIGHT50) - (dst.y * Tile.SUBTILE_HEIGHT50); + //System.out.println(new Vector2(dstX, dstY).dst(srcX, srcY)); + System.out.println(src.dst(dst)); + + path = map.path(src, dst); + break; + } + return true; + } + }); multiplexer.addProcessor(new InputAdapter() { private final float ZOOM_AMOUNT = 0.1f; @@ -321,6 +373,19 @@ public class MapViewer extends ApplicationAdapter { shapes.setAutoShapeType(true); shapes.begin(ShapeRenderer.ShapeType.Line); mapRenderer.renderDebug(shapes); + if (src != null && dst != null) { + //mapRenderer.renderDebugPath(shapes, src, dst); + if (path != null) mapRenderer.renderDebugPath2(shapes, path); + float srcX = +(src.x * Tile.SUBTILE_WIDTH50) - (src.y * Tile.SUBTILE_WIDTH50); + float srcY = -(src.x * Tile.SUBTILE_HEIGHT50) - (src.y * Tile.SUBTILE_HEIGHT50); + float dstX = +(dst.x * Tile.SUBTILE_WIDTH50) - (dst.y * Tile.SUBTILE_WIDTH50); + float dstY = -(dst.x * Tile.SUBTILE_HEIGHT50) - (dst.y * Tile.SUBTILE_HEIGHT50); + shapes.setColor(Color.WHITE); + shapes.circle(srcX, srcY, 32); + shapes.set(ShapeRenderer.ShapeType.Filled); + shapes.setColor(Color.ORANGE); + shapes.rectLine(srcX, srcY, dstX, dstY, 1); + } shapes.end(); final int width = Gdx.graphics.getWidth();