diff --git a/http-api/build.gradle b/http-api/build.gradle index 28e551d14a..5d09f50c6d 100644 --- a/http-api/build.gradle +++ b/http-api/build.gradle @@ -5,6 +5,7 @@ description = 'Web API' dependencies { annotationProcessor group: 'org.projectlombok', name: 'lombok', version: lombok + compileOnly group: 'javax.inject', name: 'javax.inject', version: javaxInject compileOnly group: 'org.projectlombok', name: 'lombok', version: lombok implementation group: 'com.google.code.gson', name: 'gson', version: gson diff --git a/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java b/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java index 6b05ad20ba..bf4698315c 100644 --- a/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java +++ b/http-api/src/main/java/net/runelite/http/api/feed/FeedClient.java @@ -28,8 +28,10 @@ import com.google.gson.JsonParseException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; @@ -39,6 +41,14 @@ public class FeedClient { private static final Logger logger = LoggerFactory.getLogger(FeedClient.class); + private final OkHttpClient client; + + @Inject + public FeedClient(OkHttpClient client) + { + this.client = client; + } + public FeedResult lookupFeed() throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -51,7 +61,7 @@ public class FeedClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java index 0e34a05742..a9361f73cb 100644 --- a/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java +++ b/http-api/src/main/java/net/runelite/http/api/item/ItemClient.java @@ -35,8 +35,10 @@ import java.lang.reflect.Type; import java.util.Arrays; import java.util.Map; import javax.imageio.ImageIO; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; @@ -46,6 +48,14 @@ public class ItemClient { private static final Logger logger = LoggerFactory.getLogger(ItemClient.class); + private final OkHttpClient client; + + @Inject + public ItemClient(OkHttpClient client) + { + this.client = client; + } + public ItemPrice lookupItemPrice(int itemId) throws IOException { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -60,7 +70,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -96,7 +106,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -129,7 +139,7 @@ public class ItemClient return Observable.defer(() -> { - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -162,7 +172,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -197,7 +207,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { @@ -233,7 +243,7 @@ public class ItemClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java index 13559bdd3b..61169ab601 100644 --- a/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java +++ b/http-api/src/main/java/net/runelite/http/api/worlds/WorldClient.java @@ -29,8 +29,10 @@ import com.google.gson.JsonParseException; import io.reactivex.Observable; import java.io.InputStream; import java.io.InputStreamReader; +import javax.inject.Inject; import net.runelite.http.api.RuneLiteAPI; import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; @@ -40,6 +42,14 @@ public class WorldClient { private static final Logger logger = LoggerFactory.getLogger(WorldClient.class); + private final OkHttpClient client; + + @Inject + public WorldClient(OkHttpClient client) + { + this.client = client; + } + public Observable lookupWorlds() { HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() @@ -54,7 +64,7 @@ public class WorldClient .url(url) .build(); - try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { diff --git a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java index c679cd319f..480478c9d0 100644 --- a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java +++ b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java @@ -24,6 +24,9 @@ */ package net.runelite.http.service.feed; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -51,7 +54,26 @@ public class FeedController private final TwitterService twitterService; private final OSRSNewsService osrsNewsService; - private FeedResult feedResult; + private static class MemoizedFeed + { + final FeedResult feedResult; + final String hash; + + MemoizedFeed(FeedResult feedResult) + { + this.feedResult = feedResult; + + Hasher hasher = Hashing.sha256().newHasher(); + for (FeedItem itemPrice : feedResult.getItems()) + { + hasher.putBytes(itemPrice.getTitle().getBytes()).putBytes(itemPrice.getContent().getBytes()); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + + private MemoizedFeed memoizedFeed; @Autowired public FeedController(BlogService blogService, TwitterService twitterService, OSRSNewsService osrsNewsService) @@ -93,20 +115,21 @@ public class FeedController log.warn(e.getMessage()); } - feedResult = new FeedResult(items); + memoizedFeed = new MemoizedFeed(new FeedResult(items)); } @GetMapping public ResponseEntity getFeed() { - if (feedResult == null) + if (memoizedFeed == null) { return ResponseEntity.notFound() .build(); } return ResponseEntity.ok() + .eTag(memoizedFeed.hash) .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) - .body(feedResult); + .body(memoizedFeed.feedResult); } } diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java index 9d1ed5710e..8db56ef2ff 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemController.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemController.java @@ -28,6 +28,9 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -53,20 +56,39 @@ public class ItemController private static final String RUNELITE_CACHE = "RuneLite-Cache"; private static final int MAX_BATCH_LOOKUP = 1024; + private static class MemoizedPrices + { + final ItemPrice[] prices; + final String hash; + + MemoizedPrices(ItemPrice[] prices) + { + this.prices = prices; + + Hasher hasher = Hashing.sha256().newHasher(); + for (ItemPrice itemPrice : prices) + { + hasher.putInt(itemPrice.getId()).putInt(itemPrice.getPrice()); + } + HashCode code = hasher.hash(); + hash = code.toString(); + } + } + private final Cache cachedEmpty = CacheBuilder.newBuilder() .maximumSize(1024L) .build(); private final ItemService itemService; - private final Supplier memorizedPrices; + private final Supplier memoizedPrices; @Autowired public ItemController(ItemService itemService) { this.itemService = itemService; - memorizedPrices = Suppliers.memoizeWithExpiration(() -> itemService.fetchPrices().stream() + memoizedPrices = Suppliers.memoizeWithExpiration(() -> new MemoizedPrices(itemService.fetchPrices().stream() .map(priceEntry -> { ItemPrice itemPrice = new ItemPrice(); @@ -76,7 +98,7 @@ public class ItemController itemPrice.setTime(priceEntry.getTime()); return itemPrice; }) - .toArray(ItemPrice[]::new), 30, TimeUnit.MINUTES); + .toArray(ItemPrice[]::new)), 30, TimeUnit.MINUTES); } @GetMapping("/{itemId}") @@ -220,8 +242,10 @@ public class ItemController @GetMapping("/prices") public ResponseEntity prices() { + MemoizedPrices memorizedPrices = this.memoizedPrices.get(); return ResponseEntity.ok() + .eTag(memorizedPrices.hash) .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES).cachePublic()) - .body(memorizedPrices.get()); + .body(memorizedPrices.prices); } } diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index 460e12132e..9723fc9605 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -26,6 +26,7 @@ package net.runelite.api; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.awt.image.BufferedImage; import javax.annotation.Nullable; import net.runelite.api.coords.LocalPoint; @@ -213,7 +214,7 @@ public interface Actor extends Entity, Locatable * @return the convex hull * @see net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); + Shape getConvexHull(); /** * Gets the world area that the actor occupies. diff --git a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java index e9ba02a18f..3686f8590d 100644 --- a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -import java.awt.Polygon; +import java.awt.Shape; /** * Represents a decorative object, such as an object on a wall. @@ -37,8 +37,8 @@ public interface DecorativeObject extends TileObject * @return the convex hull * @see api.model.Jarvis */ - Polygon getConvexHull(); - Polygon getConvexHull2(); + Shape getConvexHull(); + Shape getConvexHull2(); Entity getEntity1(); Entity getEntity2(); diff --git a/runelite-api/src/main/java/net/runelite/api/GameObject.java b/runelite-api/src/main/java/net/runelite/api/GameObject.java index adb7edd3d4..46cfbc3411 100644 --- a/runelite-api/src/main/java/net/runelite/api/GameObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GameObject.java @@ -24,7 +24,7 @@ */ package net.runelite.api; -import java.awt.Polygon; +import java.awt.Shape; import net.runelite.api.coords.Angle; /** @@ -54,19 +54,12 @@ public interface GameObject extends TileObject Point getSceneMaxLocation(); /** - * Gets the convex hull of the actors model. + * Gets the convex hull of the object's model. * * @return the convex hull * @see //net.runelite.api.model.Jarvis */ - Polygon getConvexHull(); - - /** - * Gets the polygons that make up the game object model. - * - * @return the model polygons - */ - Polygon[] getPolygons(); + Shape getConvexHull(); /** * Gets the orientation of the object. diff --git a/runelite-api/src/main/java/net/runelite/api/GroundObject.java b/runelite-api/src/main/java/net/runelite/api/GroundObject.java index bfd7fc34e2..39f6bce225 100644 --- a/runelite-api/src/main/java/net/runelite/api/GroundObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GroundObject.java @@ -24,6 +24,8 @@ */ package net.runelite.api; +import java.awt.Shape; + /** * Represents an object on the ground of a tile. */ @@ -32,4 +34,12 @@ public interface GroundObject extends TileObject Entity getEntity(); Model getModel(); + + /** + * Gets the convex hull of the objects model. + * + * @return the convex hull + * @see net.runelite.api.model.Jarvis + */ + Shape getConvexHull(); } diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index 4c08e65bf1..eae51d71e5 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -24,24 +24,22 @@ */ package net.runelite.api; -import java.awt.geom.Path2D; -import static net.runelite.api.Constants.TILE_FLAG_BRIDGE; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.Rectangle; -import java.awt.geom.Area; +import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import static net.runelite.api.Constants.TILE_FLAG_BRIDGE; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.geometry.RectangleUnion; +import net.runelite.api.geometry.Shapes; +import net.runelite.api.geometry.SimplePolygon; import net.runelite.api.model.Jarvis; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; @@ -149,7 +147,74 @@ public class Perspective } return null; + } + /** + * Translates a model's vertices into 2d space + */ + public static void modelToCanvas(Client client, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, int[] x3d, int[] y3d, int[] z3d, int[] x2d, int[] y2d) + { + final int + cameraPitch = client.getCameraPitch(), + cameraYaw = client.getCameraYaw(), + + pitchSin = SINE[cameraPitch], + pitchCos = COSINE[cameraPitch], + yawSin = SINE[cameraYaw], + yawCos = COSINE[cameraYaw], + rotateSin = SINE[rotate], + rotateCos = COSINE[rotate], + + cx = x3dCenter - client.getCameraX(), + cy = y3dCenter - client.getCameraY(), + cz = z3dCenter - client.getCameraZ(), + + viewportXMiddle = client.getViewportWidth() / 2, + viewportYMiddle = client.getViewportHeight() / 2, + viewportXOffset = client.getViewportXOffset(), + viewportYOffset = client.getViewportYOffset(), + + zoom3d = client.getScale(); + + for (int i = 0; i < end; i++) + { + int x = x3d[i]; + int y = y3d[i]; + int z = z3d[i]; + + if (rotate != 0) + { + int x0 = x; + x = x0 * rotateCos + y * rotateSin >> 16; + y = y * rotateCos - x0 * rotateSin >> 16; + } + + x += cx; + y += cy; + z += cz; + + final int + x1 = x * yawCos + y * yawSin >> 16, + y1 = y * yawCos - x * yawSin >> 16, + y2 = z * pitchCos - y1 * pitchSin >> 16, + z1 = y1 * pitchCos + z * pitchSin >> 16; + + int viewX, viewY; + + if (z1 < 50) + { + viewX = Integer.MIN_VALUE; + viewY = Integer.MIN_VALUE; + } + else + { + viewX = (viewportXMiddle + x1 * zoom3d / z1) + viewportXOffset; + viewY = (viewportYMiddle + y2 * zoom3d / z1) + viewportYOffset; + } + + x2d[i] = viewX; + y2d[i] = viewY; + } } /** @@ -490,244 +555,178 @@ public class Perspective * Get the on-screen clickable area of {@code model} as though it's for the * object on the tile at ({@code localX}, {@code localY}) and rotated to * angle {@code orientation}. - * - * @param client the game client - * @param model the model to calculate a clickbox for + * @param client the game client + * @param model the model to calculate a clickbox for * @param orientation the orientation of the model (0-2048, where 0 is north) - * @param point the coordinate of the tile + * @param point the coordinate of the tile * @return the clickable area of the model */ - public static @Nullable Area getClickbox(@Nonnull Client client, Model model, int orientation, @Nonnull LocalPoint point) + @Nullable + public static Shape getClickbox(@Nonnull Client client, Model model, int orientation, LocalPoint point) { if (model == null) { return null; } - List triangles = model.getTriangles().stream() - .map(triangle -> triangle.rotate(orientation)) - .collect(Collectors.toList()); + int x = point.getX(); + int y = point.getY(); + int z = getTileHeight(client, point, client.getPlane()); - List vertices = model.getVertices().stream() - .map(v -> v.rotate(orientation)) - .collect(Collectors.toList()); + SimplePolygon bounds = calculateAABB(client, model, orientation, x, y, z); - Area clickBox = get2DGeometry(client, triangles, point); - Area visibleAABB = getAABB(client, vertices, point); - - if (visibleAABB == null) + if (bounds == null) { return null; } - clickBox.intersect(visibleAABB); - return clickBox; - } - - /** - * Determine if a given point is off-screen. - * - * @param client - * @param point - * @return - */ - private static boolean isOffscreen(@Nonnull Client client, @Nonnull Point point) - { - return (point.getX() < 0 || point.getX() >= client.getViewportWidth()) - && (point.getY() < 0 || point.getY() >= client.getViewportHeight()); - } - - private static @Nonnull Area get2DGeometry( - @Nonnull Client client, - @Nonnull List triangles, - @Nonnull LocalPoint point - ) - { - int radius = 5; - Path2D.Double geometry = new Path2D.Double(); - - final int tileHeight = getTileHeight(client, point, client.getPlane()); - - for (Triangle triangle : triangles) + if (model.isClickable()) { - net.runelite.api.model.Vertex _a = triangle.getA(); - Point a = localToCanvas(client, - point.getX() - _a.getX(), - point.getY() - _a.getZ(), - tileHeight + _a.getY()); - if (a == null) - { - continue; - } - - Vertex _b = triangle.getB(); - Point b = localToCanvas(client, - point.getX() - _b.getX(), - point.getY() - _b.getZ(), - tileHeight + _b.getY()); - if (b == null) - { - continue; - } - - Vertex _c = triangle.getC(); - Point c = localToCanvas(client, - point.getX() - _c.getX(), - point.getY() - _c.getZ(), - tileHeight + _c.getY()); - if (c == null) - { - continue; - } - - if (isOffscreen(client, a) && isOffscreen(client, b) && isOffscreen(client, c)) - { - continue; - } - - int minX = Math.min(Math.min(a.getX(), b.getX()), c.getX()); - int minY = Math.min(Math.min(a.getY(), b.getY()), c.getY()); - - // For some reason, this calculation is always 4 pixels short of the actual in-client one - int maxX = Math.max(Math.max(a.getX(), b.getX()), c.getX()) + 4; - int maxY = Math.max(Math.max(a.getY(), b.getY()), c.getY()) + 4; - - Rectangle clickableRect = new Rectangle( - minX - radius, minY - radius, - maxX - minX + radius, maxY - minY + radius - ); - - if (geometry.contains(clickableRect)) - { - continue; - } - - geometry.append(clickableRect, false); + return bounds; } - return new Area(geometry); - } - - private static Area getAABB( - @Nonnull Client client, - @Nonnull List vertices, - @Nonnull LocalPoint point - ) - { - int maxX = 0; - int minX = 0; - int maxY = 0; - int minY = 0; - int maxZ = 0; - int minZ = 0; - - for (Vertex vertex : vertices) - { - int x = vertex.getX(); - int y = vertex.getY(); - int z = vertex.getZ(); - - if (x > maxX) - { - maxX = x; - } - if (x < minX) - { - minX = x; - } - - if (y > maxY) - { - maxY = y; - } - if (y < minY) - { - minY = y; - } - - if (z > maxZ) - { - maxZ = z; - } - if (z < minZ) - { - minZ = z; - } - } - - int centerX = (minX + maxX) / 2; - int centerY = (minY + maxY) / 2; - int centerZ = (minZ + maxZ) / 2; - - int extremeX = (maxX - minX + 1) / 2; - int extremeY = (maxY - minY + 1) / 2; - int extremeZ = (maxZ - minZ + 1) / 2; - - if (extremeX < 32) - { - extremeX = 32; - } - - if (extremeZ < 32) - { - extremeZ = 32; - } - - int x1 = point.getX() - (centerX - extremeX); - int y1 = centerY - extremeY; - int z1 = point.getY() - (centerZ - extremeZ); - - int x2 = point.getX() - (centerX + extremeX); - int y2 = centerY + extremeY; - int z2 = point.getY() - (centerZ + extremeZ); - - final int tileHeight = getTileHeight(client, point, client.getPlane()); - - Point p1 = localToCanvas(client, x1, z1, tileHeight + y1); - Point p2 = localToCanvas(client, x1, z2, tileHeight + y1); - Point p3 = localToCanvas(client, x2, z2, tileHeight + y1); - - Point p4 = localToCanvas(client, x2, z1, tileHeight + y1); - Point p5 = localToCanvas(client, x1, z1, tileHeight + y2); - Point p6 = localToCanvas(client, x1, z2, tileHeight + y2); - Point p7 = localToCanvas(client, x2, z2, tileHeight + y2); - Point p8 = localToCanvas(client, x2, z1, tileHeight + y2); - - List points = new ArrayList<>(8); - points.add(p1); - points.add(p2); - points.add(p3); - points.add(p4); - points.add(p5); - points.add(p6); - points.add(p7); - points.add(p8); - - try - { - points = Jarvis.convexHull(points); - } - catch (NullPointerException e) - { - // No non-null screen points for this AABB e.g. for an way off-screen model - return null; - } - - if (points == null) + Shapes bounds2d = calculate2DBounds(client, model, orientation, x, y, z); + if (bounds2d == null) { return null; } - Polygon hull = new Polygon(); - for (Point p : points) + for (SimplePolygon poly : bounds2d.getShapes()) { - if (p != null) - { - hull.addPoint(p.getX(), p.getY()); - } + poly.intersectWithConvex(bounds); } - return new Area(hull); + return bounds2d; + } + + private static SimplePolygon calculateAABB(Client client, Model m, int jauOrient, int x, int y, int z) + { + int ex = m.getExtremeX(); + if (ex == -1) + { + // dynamic models don't get stored when they render where this normally happens + m.calculateBoundsCylinder(); + m.calculateExtreme(0); + ex = m.getExtremeX(); + } + + int x1 = m.getCenterX(); + int y1 = m.getCenterZ(); + int z1 = m.getCenterY(); + + int ey = m.getExtremeZ(); + int ez = m.getExtremeY(); + + int x2 = x1 + ex; + int y2 = y1 + ey; + int z2 = z1 + ez; + + x1 -= ex; + y1 -= ey; + z1 -= ez; + + int[] xa = new int[]{ + x1, x2, x1, x2, + x1, x2, x1, x2 + }; + int[] ya = new int[]{ + y1, y1, y2, y2, + y1, y1, y2, y2 + }; + int[] za = new int[]{ + z1, z1, z1, z1, + z2, z2, z2, z2 + }; + + int[] x2d = new int[8]; + int[] y2d = new int[8]; + + modelToCanvas(client, 8, x, y, z, jauOrient, xa, ya, za, x2d, y2d); + + return Jarvis.convexHull(x2d, y2d); + } + + private static Shapes calculate2DBounds(Client client, Model m, int jauOrient, int x, int y, int z) + { + int[] x2d = new int[m.getVerticesCount()]; + int[] y2d = new int[m.getVerticesCount()]; + + Perspective.modelToCanvas(client, + m.getVerticesCount(), + x, y, z, + jauOrient, + m.getVerticesX(), m.getVerticesZ(), m.getVerticesY(), + x2d, y2d); + + final int radius = 5; + + int[][] tris = new int[][]{ + m.getTrianglesX(), + m.getTrianglesY(), + m.getTrianglesZ() + }; + + int vpX1 = client.getViewportXOffset(); + int vpY1 = client.getViewportXOffset(); + int vpX2 = vpX1 + client.getViewportWidth(); + int vpY2 = vpY1 + client.getViewportHeight(); + + List rects = new ArrayList<>(m.getTrianglesCount()); + + nextTri: + for (int tri = 0; tri < m.getTrianglesCount(); tri++) + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (int[] vertex : tris) + { + final int idx = vertex[tri]; + final int xs = x2d[idx]; + final int ys = y2d[idx]; + + if (xs == Integer.MIN_VALUE || ys == Integer.MIN_VALUE) + { + continue nextTri; + } + + if (xs < minX) + { + minX = xs; + } + if (xs > maxX) + { + maxX = xs; + } + if (ys < minY) + { + minY = ys; + } + if (ys > maxY) + { + maxY = ys; + } + } + + minX -= radius; + minY -= radius; + maxX += radius; + maxY += radius; + + if (vpX1 > maxX || vpX2 < minX || vpY1 > maxY || vpY2 < minY) + { + continue; + } + + RectangleUnion.Rectangle r = new RectangleUnion.Rectangle(minX, minY, maxX, maxY); + + rects.add(r); + } + + return RectangleUnion.union(rects); } /** diff --git a/runelite-api/src/main/java/net/runelite/api/TileObject.java b/runelite-api/src/main/java/net/runelite/api/TileObject.java index 604b54f51d..c78fe05622 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileObject.java +++ b/runelite-api/src/main/java/net/runelite/api/TileObject.java @@ -28,7 +28,7 @@ import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import javax.annotation.Nullable; /** @@ -132,5 +132,5 @@ public interface TileObject extends Locatable * @return the clickable area */ @Nullable - Area getClickbox(); + Shape getClickbox(); } diff --git a/runelite-api/src/main/java/net/runelite/api/WallObject.java b/runelite-api/src/main/java/net/runelite/api/WallObject.java index e5ff07a587..4e477ecc0c 100644 --- a/runelite-api/src/main/java/net/runelite/api/WallObject.java +++ b/runelite-api/src/main/java/net/runelite/api/WallObject.java @@ -24,6 +24,8 @@ */ package net.runelite.api; +import java.awt.Shape; + /** * Represents the wall of a tile, which is an un-passable boundary. */ @@ -55,4 +57,12 @@ public interface WallObject extends TileObject Model getModelA(); Model getModelB(); + + /** + * Gets the convex hull of the objects model. + * + * @return the convex hull + * @see net.runelite.api.model.Jarvis + */ + Shape getConvexHull(); } diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java b/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java new file mode 100644 index 0000000000..e71f80c6b1 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/RectangleUnion.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.geometry; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javax.annotation.Nullable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RectangleUnion +{ + private RectangleUnion() + { + } + + @RequiredArgsConstructor + @Getter + @ToString + public static class Rectangle + { + private final int x1, y1, x2, y2; + } + + /** + * Returns a polygon representing the union of all of the passed rectangles. + * the passed List will be modified + */ + @Nullable + public static Shapes union(List lefts) + { + // https://stackoverflow.com/a/35362615/2977136 + if (lefts.size() == 0) + { + return null; + } + + boolean trace = log.isTraceEnabled(); + + // Sort all of the rectangles so they are ordered by their left edge + lefts.sort(Comparator.comparing(Rectangle::getX1)); + + // Again, but for the right edge + // this should be relatively fast if the rectangles are similar sizes because timsort deals with partially + // presorted data well + List rights = new ArrayList<>(lefts); + rights.sort(Comparator.comparing(Rectangle::getX2)); + + // ranges of our scan line with how many rectangles it is occluding + Segments segments = new Segments(); + Shapes out = new Shapes<>(new ArrayList<>()); + ChangingState cs = new ChangingState(out); + + // Walk a beam left to right, colliding with any vertical edges of rectangles + for (int l = 0, r = 0; ; ) + { + Rectangle lr = null, rr = null; + if (l < lefts.size()) + { + lr = lefts.get(l); + } + if (r < rights.size()) + { + rr = rights.get(r); + } + if (lr == null && rr == null) + { + break; + } + + // get the next edge, preferring + edges + Rectangle rect; + boolean remove = lr == null || (rr != null && rr.x2 < lr.x1); + if (remove) + { + cs.delta = -1; + cs.x = rr.x2; + r++; + rect = rr; + } + else + { + cs.delta = 1; + cs.x = lr.x1; + l++; + rect = lr; + } + if (trace) + { + log.trace("{}{}", remove ? "-" : "+", rect); + } + + int y1 = rect.y1; + int y2 = rect.y2; + + // Find or create the y1 edge + Segment n = segments.findLE(y1); + if (n == null) + { + n = segments.insertAfter(null, y1); + } + if (n.y != y1) + { + n = segments.insertAfter(n, y1); + n.value = n.previous.value; + } + + for (; ; ) + { + // create the y2 edge if the next edge is past + if (n.next == null || n.next.y > y2) + { + segments.insertAfter(n, y2); + } + cs.touch(n); + n = n.next; + if (n.y == y2) + { + cs.finish(n); + + if (trace) + { + for (Segment s = segments.first; s != null; s = s.next) + { + String chunk = ""; + if (s.chunk != null) + { + chunk = (s.left ? ">" : "[") + System.identityHashCode(s.chunk) + (s.left ? "]" : "<"); + } + log.trace("{} = {} {}", s.y, s.value, chunk); + } + log.trace(""); + } + break; + } + } + } + + assert segments.allZero(); + + return out; + } + + @RequiredArgsConstructor + private static class ChangingState + { + final Shapes out; + + int x; + int delta; + + Segment first; + + void touch(Segment s) + { + int oldValue = s.value; + s.value += delta; + if (oldValue <= 0 ^ s.value <= 0) + { + if (first == null) + { + first = s; + } + } + else + { + finish(s); + } + } + + void finish(Segment s) + { + if (first == null) + { + return; + } + + if (first.chunk != null && s.chunk != null) + { + push(first); + push(s); + + if (first.chunk == s.chunk) + { + Chunk c = first.chunk; + first.chunk = null; + s.chunk = null; + c.left = null; + c.right = null; + out.getShapes().add(c); + } + else + { + Chunk leftChunk, rightChunk; + if (!s.left) + { + leftChunk = s.chunk; + rightChunk = first.chunk; + } + else + { + leftChunk = first.chunk; + rightChunk = s.chunk; + } + + log.trace("Joining {} onto {}", System.identityHashCode(rightChunk), System.identityHashCode(leftChunk)); + if (first.left == s.left) + { + log.trace("reverse"); + if (first.left) + { + leftChunk.reverse(); + } + else + { + rightChunk.reverse(); + } + } + log.trace("{} {}", first.y, s.y); + rightChunk.appendTo(leftChunk); + + first.chunk = null; + s.chunk = null; + leftChunk.right.chunk = null; + rightChunk.left.chunk = null; + leftChunk.right = rightChunk.right; + leftChunk.left.chunk = leftChunk; + leftChunk.right.chunk = leftChunk; + } + } + else if (first.chunk == null && s.chunk == null) + { + first.chunk = new Chunk(); + first.chunk.right = first; + first.left = false; + s.chunk = first.chunk; + first.chunk.left = s; + s.left = true; + + push(first); + push(s); + } + else if (first.chunk == null) + { + push(s); + move(first, s); + push(first); + } + else + { + push(first); + move(s, first); + push(s); + } + + first = null; + } + + private void move(Segment dst, Segment src) + { + dst.chunk = src.chunk; + dst.left = src.left; + src.chunk = null; + if (dst.left) + { + assert dst.chunk.left == src; + dst.chunk.left = dst; + } + else + { + assert dst.chunk.right == src; + dst.chunk.right = dst; + } + } + + private void push(Segment s) + { + if (s.left) + { + s.chunk.pushLeft(x, s.y); + assert s.chunk.left == s; + } + else + { + s.chunk.pushRight(x, s.y); + assert s.chunk.right == s; + } + } + } + + @NoArgsConstructor + private static class Segment + { + Segment next, previous; + + Chunk chunk; + boolean left; + int y; + int value; + } + + @NoArgsConstructor + private static class Segments + { + Segment first; + + Segment findLE(int y) + { + Segment s = first; + if (s == null || s.y > y) + { + return null; + } + for (; ; ) + { + if (s.y == y) + { + return s; + } + + Segment n = s.next; + if (n == null || n.y > y) + { + return s; + } + + s = n; + } + } + + Segment insertAfter(Segment before, int y) + { + Segment n = new Segment(); + n.y = y; + if (before != null) + { + if (before.next != null) + { + n.next = before.next; + n.next.previous = n; + } + n.value = before.value; + before.next = n; + n.previous = before; + } + else + { + if (first != null) + { + n.next = first; + first.previous = n; + } + first = n; + } + return n; + } + + boolean allZero() + { + for (Segment s = first; s != null; s = s.next) + { + if (s.value != 0 || s.chunk != null) + { + return false; + } + } + return true; + } + } + + private static class Chunk extends SimplePolygon + { + Segment left, right; + + @Override + public void reverse() + { + super.reverse(); + assert right.left == false; + assert left.left == true; + Segment tr = left; + left = right; + right = tr; + right.left = false; + left.left = true; + } + } +} diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java b/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java new file mode 100644 index 0000000000..2e02a1cf62 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/Shapes.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.geometry; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Shapes implements Shape +{ + public Shapes(T ...shape) + { + this(Arrays.asList(shape)); + } + + @Getter + private final List shapes; + + @Override + public Rectangle getBounds() + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (Shape shape : shapes) + { + Rectangle bounds = shape.getBounds(); + minX = Math.min(bounds.x, minX); + minY = Math.min(bounds.y, minY); + maxX = Math.max(bounds.x + bounds.width, maxX); + maxY = Math.max(bounds.y + bounds.height, maxY); + } + + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public Rectangle2D getBounds2D() + { + double + minX = Double.MAX_VALUE, + minY = Double.MAX_VALUE, + maxX = Double.MIN_VALUE, + maxY = Double.MIN_VALUE; + + for (Shape shape : shapes) + { + Rectangle2D bounds = shape.getBounds2D(); + minX = Math.min(bounds.getX(), minX); + minY = Math.min(bounds.getY(), minY); + maxX = Math.max(bounds.getMaxX(), maxX); + maxY = Math.max(bounds.getMaxY(), maxY); + } + + return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public boolean contains(double x, double y) + { + return shapes.stream().anyMatch(s -> s.contains(x, y)); + } + + @Override + public boolean contains(Point2D p) + { + return shapes.stream().anyMatch(s -> s.contains(p)); + } + + @Override + public boolean intersects(double x, double y, double w, double h) + { + return shapes.stream().anyMatch(s -> s.intersects(x, y, w, h)); + } + + @Override + public boolean intersects(Rectangle2D r) + { + return shapes.stream().anyMatch(s -> s.intersects(r)); + } + + @Override + public boolean contains(double x, double y, double w, double h) + { + return shapes.stream().anyMatch(s -> s.contains(x, y, w, h)); + } + + @Override + public boolean contains(Rectangle2D r) + { + return shapes.stream().anyMatch(s -> s.contains(r)); + } + + @Override + public PathIterator getPathIterator(AffineTransform at) + { + return new ShapeIterator(shapes.stream() + .map(s -> s.getPathIterator(at)) + .iterator()); + } + + @Override + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return new ShapeIterator(shapes.stream() + .map(s -> s.getPathIterator(at, flatness)) + .iterator()); + } + + private static class ShapeIterator implements PathIterator + { + private final Iterator iter; + private PathIterator current = null; + private final int windingRule; + + ShapeIterator(Iterator iter) + { + this.iter = iter; + if (iter.hasNext()) + { + current = iter.next(); + windingRule = current.getWindingRule(); + checkDone(); + } + else + { + windingRule = 0; + } + } + + @Override + public int getWindingRule() + { + return windingRule; + } + + @Override + public boolean isDone() + { + return current == null; + } + + @Override + public void next() + { + current.next(); + checkDone(); + } + + private void checkDone() + { + for (; current != null && current.isDone(); ) + { + if (iter.hasNext()) + { + current = iter.next(); + assert windingRule == current.getWindingRule(); + } + else + { + current = null; + } + } + } + + @Override + public int currentSegment(float[] coords) + { + return current.currentSegment(coords); + } + + @Override + public int currentSegment(double[] coords) + { + return current.currentSegment(coords); + } + } +} diff --git a/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java b/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java new file mode 100644 index 0000000000..bfd136d948 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/geometry/SimplePolygon.java @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.geometry; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.Point; + +/** + * A simple list of vertices that can be append or prepended to + */ +@AllArgsConstructor +@Getter +@Setter +public class SimplePolygon implements Shape +{ + private static final int GROW = 16; + + protected int[] x, y; + protected int left, right; + + public SimplePolygon() + { + this(new int[32], new int[32], 16, 15); + } + + public SimplePolygon(int[] x, int[] y, int length) + { + this(x, y, 0, length - 1); + } + + public void pushLeft(int xCoord, int yCoord) + { + left--; + if (left < 0) + { + expandLeft(GROW); + } + x[left] = xCoord; + y[left] = yCoord; + } + + public void popLeft() + { + left++; + } + + protected void expandLeft(int grow) + { + int[] nx = new int[x.length + grow]; + System.arraycopy(x, 0, nx, grow, x.length); + x = nx; + int[] ny = new int[nx.length]; + System.arraycopy(y, 0, ny, grow, y.length); + y = ny; + left += grow; + right += grow; + } + + public void pushRight(int xCoord, int yCoord) + { + right++; + if (right >= x.length) + { + expandRight(GROW); + } + x[right] = xCoord; + y[right] = yCoord; + } + + public void popRight() + { + right--; + } + + protected void expandRight(int grow) + { + int[] nx = new int[x.length + grow]; + System.arraycopy(x, 0, nx, 0, x.length); + x = nx; + int[] ny = new int[nx.length]; + System.arraycopy(y, 0, ny, 0, y.length); + y = ny; + } + + public int getX(int index) + { + return x[left + index]; + } + + public int getY(int index) + { + return y[left + index]; + } + + public int size() + { + return right - left + 1; + } + + public List toRuneLitePointList() + { + List out = new ArrayList<>(size()); + for (int i = left; i <= right; i++) + { + out.add(new Point(x[i], y[i])); + } + return out; + } + + public void copyTo(int[] xDest, int[] yDest, int offset) + { + System.arraycopy(x, left, xDest, offset, size()); + System.arraycopy(y, left, yDest, offset, size()); + } + + public void appendTo(SimplePolygon other) + { + int size = size(); + if (size <= 0) + { + return; + } + other.expandRight(size); + copyTo(other.x, other.y, other.right + 1); + other.right += size; + } + + public void reverse() + { + int half = size() / 2; + for (int i = 0; i < half; i++) + { + int li = left + i; + int ri = right - i; + int tx = x[li]; + int ty = y[li]; + x[li] = x[ri]; + y[li] = y[ri]; + x[ri] = tx; + y[ri] = ty; + } + } + + /** + * Clips the polygon with the passed convex polygon + */ + public void intersectWithConvex(SimplePolygon convex) + { + // Sutherland-Hodgman + int[] tx = new int[size()]; + int[] ty = new int[tx.length]; + + int cx1 = convex.x[convex.right]; + int cy1 = convex.y[convex.right]; + for (int ci = convex.left; ci <= convex.right; ci++) + { + if (size() < 3) + { + return; + } + + int tRight = this.right; + int tLeft = this.left; + + int[] tmpX = x; + int[] tmpY = y; + + this.x = tx; + this.y = ty; + this.left = 0; + this.right = -1; + tx = tmpX; + ty = tmpY; + + int cx2 = convex.x[ci]; + int cy2 = convex.y[ci]; + + int tx1 = tx[tRight]; + int ty1 = ty[tRight]; + + for (int ti = tLeft; ti <= tRight; ti++) + { + int tx2 = tx[ti]; + int ty2 = ty[ti]; + + int p1 = (cx2 - cx1) * (ty1 - cy1) - (cy2 - cy1) * (tx1 - cx1); + int p2 = (cx2 - cx1) * (ty2 - cy1) - (cy2 - cy1) * (tx2 - cx1); + + if (p1 < 0 && p2 < 0) + { + pushRight(tx2, ty2); + } + else if (p1 >= 0 != p2 >= 0) + { + long nota = cx1 * cy2 - cy1 * cx2; + long clue = tx1 * ty2 - ty1 * tx2; + long div = ((cx1 - cx2) * (ty1 - ty2) - (cy1 - cy2) * (tx1 - tx2)); + pushRight((int) ((nota * (tx1 - tx2) - (cx1 - cx2) * clue) / div), + (int) ((nota * (ty1 - ty2) - (cy1 - cy2) * clue) / div)); + + if (p1 >= 0) + { + pushRight(tx2, ty2); + } + } + + tx1 = tx2; + ty1 = ty2; + } + + cx1 = cx2; + cy1 = cy2; + } + } + + @Override + public Rectangle getBounds() + { + int + minX = Integer.MAX_VALUE, + minY = Integer.MAX_VALUE, + maxX = Integer.MIN_VALUE, + maxY = Integer.MIN_VALUE; + + for (int i = left; i <= right; i++) + { + final int xs = x[i]; + final int ys = y[i]; + + if (xs < minX) + { + minX = xs; + } + if (xs > maxX) + { + maxX = xs; + } + if (ys < minY) + { + minY = ys; + } + if (ys > maxY) + { + maxY = ys; + } + } + + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + @Override + public Rectangle2D getBounds2D() + { + Rectangle b = getBounds(); + return new Rectangle2D.Float(b.x, b.y, b.width, b.height); + } + + @Override + public boolean contains(double cx, double cy) + { + if (size() < 3) + { + return false; + } + + return (crossings(cx, cy, false) & 1) != 0; + } + + private int crossings(double cx, double cy, boolean swap) + { + int collisions = 0; + + int[] x = this.x; + int[] y = this.y; + if (swap) + { + y = this.x; + x = this.y; + } + + for (int x0 = x[right], y0 = y[right], x1, y1, i = left; i <= right; i++, x0 = x1, y0 = y1) + { + x1 = x[i]; + y1 = y[i]; + + if (y0 == y1) + { + continue; + } + + double dy0 = y0, dy1 = y1; + + if (cy <= dy0 == cy <= dy1) + { + continue; + } + + double dx0 = x0, dx1 = x1; + + boolean left = cx < dx0; + if (left == cx < dx1) + { + if (!left) + { + collisions++; + } + continue; + } + + if ((dx1 - dx0) * (cy - dy0) - (cx - dx0) * (dy1 - dy0) > 0 == dy0 > dy1) + { + collisions++; + } + } + return collisions; + } + + @Override + public boolean contains(Point2D p) + { + return contains(p.getX(), p.getY()); + } + + @Override + public boolean intersects(double x0, double y0, double w, double h) + { + // this is horribly inefficient, but I don't think it will be called anywhere + + double x1 = x0 + w; + double y1 = y0 + h; + + return crossings(x0, y0, false) != crossings(x1, y0, false) // top + || crossings(x0, y1, false) != crossings(x1, y1, false) // bottom + || crossings(x0, y0, true) != crossings(x0, y1, true) // left + || crossings(x1, y0, true) != crossings(x1, y1, true); // right + + } + + @Override + public boolean intersects(Rectangle2D r) + { + return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override + public boolean contains(double x, double y, double w, double h) + { + if (!getBounds().contains(x, y, w, h)) + { + return false; + } + + return !intersects(x, y, w, h); + } + + @Override + public boolean contains(Rectangle2D r) + { + return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + + @Override + public PathIterator getPathIterator(AffineTransform at) + { + if (at == null) + { + return new SimpleIterator(); + } + return new TransformIterator(at); + } + + @Override + public PathIterator getPathIterator(AffineTransform at, double flatness) + { + return getPathIterator(at); + } + + private class SimpleIterator implements PathIterator + { + private int i = -1; + + @Override + public int getWindingRule() + { + return WIND_EVEN_ODD; + } + + @Override + public boolean isDone() + { + return size() == 0 || i > right; + } + + @Override + public void next() + { + if (i == -1) + { + i = left; + } + else + { + i++; + } + } + + @Override + public int currentSegment(float[] coords) + { + if (i == -1) + { + coords[0] = x[right]; + coords[1] = y[right]; + return SEG_MOVETO; + } + + coords[0] = x[i]; + coords[1] = y[i]; + return SEG_LINETO; + } + + @Override + public int currentSegment(double[] coords) + { + if (i == -1) + { + coords[0] = x[right]; + coords[1] = y[right]; + return SEG_MOVETO; + } + + coords[0] = x[i]; + coords[1] = y[i]; + return SEG_LINETO; + } + } + + private class TransformIterator extends SimpleIterator + { + private final AffineTransform transform; + + TransformIterator(AffineTransform transform) + { + this.transform = transform; + } + + @Override + public int currentSegment(float[] coords) + { + int v = super.currentSegment(coords); + transform.transform(coords, 0, coords, 0, 2); + return v; + } + + @Override + public int currentSegment(double[] coords) + { + int v = super.currentSegment(coords); + transform.transform(coords, 0, coords, 0, 2); + return v; + } + } +} diff --git a/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java b/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java index 5435470c37..3d2cd2b6aa 100644 --- a/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java +++ b/runelite-api/src/main/java/net/runelite/api/model/Jarvis.java @@ -24,9 +24,9 @@ */ package net.runelite.api.model; -import net.runelite.api.Point; -import java.util.ArrayList; import java.util.List; +import net.runelite.api.Point; +import net.runelite.api.geometry.SimplePolygon; /** * Provides utility methods for computing the convex hull of a list of @@ -41,91 +41,147 @@ public class Jarvis /** * Computes and returns the convex hull of the passed points. *

- * The size of the list must be at least 4, otherwise this method will + * The size of the list must be at least 3, otherwise this method will * return null. * * @param points list of points * @return list containing the points part of the convex hull */ + @Deprecated public static List convexHull(List points) { - if (points.size() < 3) + int[] xs = new int[points.size()]; + int[] ys = new int[xs.length]; + for (int i = 0; i < xs.length; i++) + { + Point p = points.get(i); + xs[i] = p.getX(); + ys[i] = p.getY(); + } + + SimplePolygon poly = convexHull(xs, ys); + if (poly == null) { return null; } - List ch = new ArrayList<>(); + return poly.toRuneLitePointList(); + } + + /** + * Computes and returns the convex hull of the passed points. + *

+ * The size of the list must be at least 3, otherwise this method will + * return null. + * + * @return a shape the points part of the convex hull + */ + public static SimplePolygon convexHull(int[] xs, int[] ys) + { + int length = xs.length; + + // remove any invalid entries + { + int i = 0, offset = 0; + for (; i < length; i++) + { + if (xs[i] == Integer.MIN_VALUE) + { + offset++; + i++; + break; + } + } + for (; i < length; i++) + { + if (xs[i] == Integer.MIN_VALUE) + { + offset++; + continue; + } + xs[i - offset] = xs[i]; + ys[i - offset] = ys[i]; + } + length -= offset; + } + + if (length < 3) + { + return null; + } // find the left most point - Point left = findLeftMost(points); + int left = findLeftMost(xs, ys, length); // current point we are on - Point current = left; + int current = left; + + SimplePolygon out = new SimplePolygon(new int[16], new int[16], 0); do { - ch.add(current); - assert ch.size() <= points.size() : "hull has more points than graph"; - if (ch.size() > points.size()) + int cx = xs[current]; + int cy = ys[current]; + out.pushRight(cx, cy); + + if (out.size() > length) { - // Just to make sure we never somehow get stuck in this loop return null; } // the next point - all points are to the right of the // line between current and next - Point next = null; + int next = 0; + int nx = xs[next]; + int ny = ys[next]; - for (Point p : points) + for (int i = 1; i < length; i++) { - if (next == null) + long cp = crossProduct(cx, cy, xs[i], ys[i], nx, ny); + if (cp > 0 || (cp == 0 && square(cx - xs[i]) + square(cy - ys[i]) > square(cx - nx) + square(cy - ny))) { - next = p; - continue; + next = i; + nx = xs[next]; + ny = ys[next]; } - - long cp = crossProduct(current, p, next); - if (cp > 0 || (cp == 0 && current.distanceTo(p) > current.distanceTo(next))) - { - next = p; - } - } - - // Points can be null if they are behind or very close to the camera. - if (next == null) - { - return null; } current = next; } while (current != left); - return ch; + return out; } - private static Point findLeftMost(List points) + private static int square(int x) { - Point left = null; + return x * x; + } - for (Point p : points) + private static int findLeftMost(int[] xs, int[] ys, int length) + { + int idx = 0; + int x = xs[idx]; + int y = ys[idx]; + + for (int i = 1; i < length; i++) { - if (left == null || p.getX() < left.getX()) + int ix = xs[i]; + if (ix < x || ix == x && ys[i] < y) { - left = p; - } - else if (p.getX() == left.getX() && p.getY() < left.getY()) - { - left = p; + idx = i; + x = xs[idx]; + y = ys[idx]; } } - return left; + return idx; } - private static long crossProduct(Point p, Point q, Point r) + private static long crossProduct(int px, int py, int qx, int qy, int rx, int ry) { - return (long)(q.getY() - p.getY()) * (r.getX() - q.getX()) - - (long)(q.getX() - p.getX()) * (r.getY() - q.getY()); + long val = (long) (qy - py) * (rx - qx) + - (long) (qx - px) * (ry - qy); + return val; } -} +} \ No newline at end of file diff --git a/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java b/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java new file mode 100644 index 0000000000..f8d714180b --- /dev/null +++ b/runelite-api/src/test/java/net/runelite/api/geometry/RectangleUnionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.geometry; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import javax.imageio.ImageIO; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; + +@Slf4j +public class RectangleUnionTest +{ + private static final int ITERATIONS = 100; + private static final int WIDTH = 1000; + private static final int MAX_RECTS = 50; + + // @Test + public void test() throws IOException + { + for (int count = 1; count < MAX_RECTS; count++) + { + for (int r = 0; r < ITERATIONS; r++) + { + Random rand = new Random(count << 16 | r); + String id = count + "rects_iteration" + r; + log.info(id); + BufferedImage wanted = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_BYTE_BINARY); + BufferedImage got = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_BYTE_BINARY); + + Graphics2D wg = wanted.createGraphics(); + wg.setColor(Color.WHITE); + Graphics2D gg = got.createGraphics(); + gg.setColor(Color.WHITE); + + List rects = new ArrayList<>(count); + + for (int i = 0; i < count; i++) + { + int x1, y1, x2, y2; + + do + { + x1 = rand.nextInt(WIDTH); + x2 = rand.nextInt(WIDTH); + } + while (x1 >= x2); + + do + { + y1 = rand.nextInt(WIDTH); + y2 = rand.nextInt(WIDTH); + } + while (y1 >= y2); + + RectangleUnion.Rectangle rect = new RectangleUnion.Rectangle(x1, y1, x2, y2); + log.trace("{}", rect); + rects.add(rect); + + wg.fillRect(x1, y1, x2 - x1, y2 - y1); + } + + Shape union = RectangleUnion.union(rects); + + gg.fill(union); + + loop: + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < WIDTH; y++) + { + if (wanted.getRGB(x, y) != got.getRGB(x, y)) + { + File tmp = new File(System.getProperty("java.io.tmpdir")); + ImageIO.write(wanted, "png", new File(tmp, id + "_wanted.png")); + ImageIO.write(got, "png", new File(tmp, id + "_got.png")); + + Assert.fail(id); + break loop; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java index 611064f162..d6749dcdb3 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteModule.java @@ -28,6 +28,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.google.inject.name.Names; import java.applet.Applet; +import java.io.File; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nullable; @@ -51,12 +52,15 @@ import net.runelite.client.task.Scheduler; import net.runelite.client.util.DeferredEventBus; import net.runelite.client.util.ExecutorServiceExceptionLogger; import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Cache; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RuneLiteModule extends AbstractModule { + private static final int MAX_OKHTTP_CACHE_SIZE = 20 * 1024 * 1024; // 20mb + private final ClientUpdateCheckMode updateCheckMode; private final boolean developerMode; @@ -72,7 +76,9 @@ public class RuneLiteModule extends AbstractModule bindConstant().annotatedWith(Names.named("updateCheckMode")).to(updateCheckMode); bindConstant().annotatedWith(Names.named("developerMode")).to(developerMode); bind(ScheduledExecutorService.class).toInstance(new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor())); - bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT); + bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT.newBuilder() + .cache(new Cache(new File(RuneLite.RUNELITE_DIR, "cache" + File.separator + "okhttp"), MAX_OKHTTP_CACHE_SIZE)) + .build()); bind(MenuManager.class); bind(ChatMessageManager.class); bind(ItemManager.class); diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index 3b8df74e93..ae4885842d 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -136,7 +136,7 @@ public class ItemManager private final Client client; private final ScheduledExecutorService scheduledExecutorService; private final ClientThread clientThread; - private final ItemClient itemClient = new ItemClient(); + private final ItemClient itemClient; private final ImmutableMap itemStatMap; private final LoadingCache itemImages; private final LoadingCache itemDefinitions; @@ -149,12 +149,14 @@ public class ItemManager Client client, ScheduledExecutorService executor, ClientThread clientThread, - EventBus eventbus + EventBus eventbus, + ItemClient itemClient ) { this.client = client; this.scheduledExecutorService = executor; this.clientThread = clientThread; + this.itemClient = itemClient; scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES); scheduledExecutorService.submit(this::loadStats); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java index 7180914d79..9323ae0b44 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java @@ -29,7 +29,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @@ -90,7 +90,7 @@ class AgilityOverlay extends Overlay } return; } - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { AgilityShortcut agilityShortcut = obstacle.getShortcut(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java index 4e7b818f90..7d2dce821d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/blastfurnace/BlastFurnaceClickBoxOverlay.java @@ -27,7 +27,7 @@ package net.runelite.client.plugins.blastfurnace; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Shape; import javax.inject.Inject; import javax.inject.Singleton; import net.runelite.api.Client; @@ -110,7 +110,7 @@ class BlastFurnaceClickBoxOverlay extends Overlay if (localLocation.distanceTo(location) <= MAX_DISTANCE) { - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java index 39614234bd..cb5272a491 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/defaultworld/DefaultWorldPlugin.java @@ -63,7 +63,9 @@ public class DefaultWorldPlugin extends Plugin @Inject private ClientThread clientThread; - private final WorldClient worldClient = new WorldClient(); + @Inject + private WorldClient worldClient; + private int worldCache; private boolean worldChangeRequired; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index 268f7784ff..eba53c5838 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -32,6 +32,7 @@ import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; +import java.awt.Shape; import java.awt.geom.Rectangle2D; import java.util.List; import javax.inject.Inject; @@ -326,10 +327,10 @@ class DevToolsOverlay extends Overlay // Draw a polygon around the convex hull // of the model vertices - Polygon p = gameObject.getConvexHull(); + Shape p = gameObject.getConvexHull(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } // This is incredibly taxing to run, only uncomment if you know what you're doing. /*renderGameObjectWireframe(graphics, gameObject, Color.CYAN);*/ @@ -372,16 +373,16 @@ class DevToolsOverlay extends Overlay OverlayUtil.renderTileOverlay(graphics, decorObject, "ID: " + decorObject.getId(), DEEP_PURPLE); } - Polygon p = decorObject.getConvexHull(); + Shape p = decorObject.getConvexHull(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } p = decorObject.getConvexHull2(); if (p != null) { - graphics.drawPolygon(p); + graphics.draw(p); } } } @@ -553,23 +554,6 @@ class DevToolsOverlay extends Overlay graphics.drawString(text, textX, textY); } - private void renderGameObjectWireframe(Graphics2D graphics, GameObject gameObject, Color color) - { - Polygon[] polys = gameObject.getPolygons(); - - if (polys == null) - { - return; - } - - graphics.setColor(color); - - for (Polygon p : polys) - { - graphics.drawPolygon(p); - } - } - private void renderPlayerWireframe(Graphics2D graphics, Player player, Color color) { Polygon[] polys = player.getPolygons(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java index f7a6337aa5..6032056fc8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/feed/FeedPlugin.java @@ -66,13 +66,15 @@ public class FeedPlugin extends Plugin @Inject private ScheduledExecutorService executorService; + @Inject + private FeedClient feedClient; + @Inject private EventBus eventBus; private FeedPanel feedPanel; private NavigationButton navButton; - private final FeedClient feedClient = new FeedClient(); private final Supplier feedSupplier = Suppliers.memoizeWithExpiration(() -> { try diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gauntlet/GauntletOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/gauntlet/GauntletOverlay.java index 5345b5ced3..a65a72a335 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gauntlet/GauntletOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gauntlet/GauntletOverlay.java @@ -32,6 +32,7 @@ import java.awt.Font; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; +import java.awt.Shape; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; @@ -201,7 +202,7 @@ public class GauntletOverlay extends Overlay if (plugin.isOverlayBoss()) { - Polygon polygon = boss.getConvexHull(); + Shape polygon = boss.getConvexHull(); if (polygon == null) { @@ -315,7 +316,7 @@ public class GauntletOverlay extends Overlay { // Don't use Convex Hull click box. As the room start to fill up, your FPS will dip. - Polygon polygon = object.getGameObject().getConvexHull(); + Shape polygon = object.getGameObject().getConvexHull(); if (polygon == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java index a9a687d3bc..89d3cf051d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcSceneOverlay.java @@ -30,6 +30,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.text.DecimalFormat; import java.text.NumberFormat; import java.time.Instant; @@ -46,12 +47,12 @@ import net.runelite.api.Point; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.util.Text; import net.runelite.client.graphics.ModelOutlineRenderer; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayUtil; -import net.runelite.api.util.Text; @Singleton public class NpcSceneOverlay extends Overlay @@ -181,8 +182,9 @@ public class NpcSceneOverlay extends Overlay renderPoly(graphics, color, tilePoly); break; case HULL: - final Polygon objectClickbox = actor.getConvexHull(); - renderPoly(graphics, color, objectClickbox); + final Shape objectClickbox = actor.getConvexHull(); + graphics.setColor(color); + graphics.draw(objectClickbox); break; case THIN_OUTLINE: modelOutliner.drawOutline(actor, 1, color); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index e9ac6d354a..7e5e58701d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -28,8 +28,7 @@ package net.runelite.client.plugins.objectindicators; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import static java.lang.Math.floor; import javax.inject.Inject; import javax.inject.Singleton; @@ -101,8 +100,8 @@ class ObjectIndicatorsOverlay extends Overlay } break; case HULL: - final Polygon polygon; - Polygon polygon2 = null; + final Shape polygon; + Shape polygon2 = null; if (object instanceof GameObject) { @@ -129,7 +128,7 @@ class ObjectIndicatorsOverlay extends Overlay } break; case CLICKBOX: - Area clickbox = object.getClickbox(); + Shape clickbox = object.getClickbox(); if (clickbox != null) { OverlayUtil.renderHoverableArea(graphics, object.getClickbox(), client.getMouseCanvasPosition(), TRANSPARENT, objectColor, objectColor.darker()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/GangplankOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/GangplankOverlay.java index 31191088bd..1e1b24b13a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/GangplankOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/GangplankOverlay.java @@ -30,7 +30,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.Polygon; +import java.awt.Shape; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.Player; @@ -92,12 +92,12 @@ public class GangplankOverlay extends Overlay if (noviceGangplankTile != null) { - Polygon polygon = noviceGangplankTile.getGameObjects()[0].getConvexHull(); + Shape polygon = noviceGangplankTile.getGameObjects()[0].getConvexHull(); if (polygon != null) { graphics.setColor(noviceCbColor); graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(polygon); + graphics.draw(polygon); graphics.setColor(setColorAlpha(noviceCbColor, 45)); graphics.fill(polygon); @@ -112,12 +112,12 @@ public class GangplankOverlay extends Overlay if (intermediateGangplankTile != null) { - Polygon polygon = intermediateGangplankTile.getGameObjects()[0].getConvexHull(); + Shape polygon = intermediateGangplankTile.getGameObjects()[0].getConvexHull(); if (polygon != null) { graphics.setColor(intermediateCbColor); graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(polygon); + graphics.draw(polygon); graphics.setColor(setColorAlpha(intermediateCbColor, 45)); graphics.fill(polygon); @@ -132,12 +132,12 @@ public class GangplankOverlay extends Overlay if (veteranGangplankTile != null) { - Polygon polygon = veteranGangplankTile.getGameObjects()[0].getConvexHull(); + Shape polygon = veteranGangplankTile.getGameObjects()[0].getConvexHull(); if (polygon != null) { graphics.setColor(veteranCbColor); graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(polygon); + graphics.draw(polygon); graphics.setColor(setColorAlpha(veteranCbColor, 45)); graphics.fill(polygon); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/NpcHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/NpcHighlightOverlay.java index 540134714b..b66085995d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/NpcHighlightOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/NpcHighlightOverlay.java @@ -31,6 +31,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -141,7 +142,7 @@ public class NpcHighlightOverlay extends Overlay private void renderHullOverlay(Graphics2D graphics, NPC npc, Color color) { - Polygon objectClickbox = npc.getConvexHull(); + Shape objectClickbox = npc.getConvexHull(); if (objectClickbox != null) { graphics.setColor(color); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/RepairOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/RepairOverlay.java index 458337fb33..2e96070a4f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/RepairOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/RepairOverlay.java @@ -30,7 +30,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Shape; import net.runelite.api.Client; import net.runelite.api.Constants; import net.runelite.api.GameObject; @@ -185,7 +185,7 @@ public class RepairOverlay extends Overlay return null; } - private void renderObjectOverlay(Graphics2D graphics, Area area, Color color, Point mousePosition) + private void renderObjectOverlay(Graphics2D graphics, Shape area, Color color, Point mousePosition) { if (area == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java index 99ea4f9fcb..971b765126 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/pyramidplunder/PyramidPlunderOverlay.java @@ -27,7 +27,7 @@ package net.runelite.client.plugins.pyramidplunder; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.geom.Area; +import java.awt.Shape; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Locale; @@ -119,7 +119,7 @@ public class PyramidPlunderOverlay extends Overlay objectID = impostor.getId(); } - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { Color configColor = Color.GREEN; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/shortcuts/ShortcutOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/shortcuts/ShortcutOverlay.java index 2ba5cd8d1b..f98f739f2d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/shortcuts/ShortcutOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/shortcuts/ShortcutOverlay.java @@ -2,7 +2,7 @@ package net.runelite.client.plugins.raids.shortcuts; import java.awt.Dimension; import java.awt.Graphics2D; -import java.awt.Polygon; +import java.awt.Shape; import java.awt.image.BufferedImage; import javax.inject.Inject; import javax.inject.Singleton; @@ -48,7 +48,7 @@ public class ShortcutOverlay extends Overlay { if (shortcut.getPlane() == client.getPlane()) { - Polygon poly; + Shape poly; if ((shortcut instanceof GameObject)) { poly = ((GameObject) shortcut).getConvexHull(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java index 2a4775fdce..dd9c5f6a33 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/roguesden/RoguesDenOverlay.java @@ -28,7 +28,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import javax.inject.Inject; import javax.inject.Singleton; import net.runelite.api.Client; @@ -74,7 +74,7 @@ public class RoguesDenOverlay extends Overlay { if (tile.getPlane() == client.getPlane()) { - final Area clickBox = obstacle.getClickbox(); + final Shape clickBox = obstacle.getClickbox(); if (clickBox != null) { final Point mouse = client.getMouseCanvasPosition(); @@ -93,7 +93,7 @@ public class RoguesDenOverlay extends Overlay } else { - Polygon p; + Shape p; if (obstacle instanceof GameObject) { p = ((GameObject) obstacle).getConvexHull(); @@ -106,7 +106,7 @@ public class RoguesDenOverlay extends Overlay if (p != null) { graphics.setColor(OBJECT_COLOR); - graphics.drawPolygon(p); + graphics.draw(p); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java index 9dd2dd5e8d..6336656d6d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/AbyssOverlay.java @@ -30,7 +30,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; -import java.awt.geom.Area; +import java.awt.Shape; import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.NPC; @@ -105,7 +105,7 @@ class AbyssOverlay extends Overlay } Point mousePosition = client.getMouseCanvasPosition(); - Area objectClickbox = object.getClickbox(); + Shape objectClickbox = object.getClickbox(); if (objectClickbox != null) { if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java index c8a1d223b6..f199816f21 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetClickboxOverlay.java @@ -31,6 +31,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.util.List; import java.util.Set; import javax.inject.Inject; @@ -43,12 +44,12 @@ import net.runelite.api.Point; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.util.Text; import net.runelite.client.graphics.ModelOutlineRenderer; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayUtil; -import net.runelite.api.util.Text; @Singleton public class TargetClickboxOverlay extends Overlay @@ -129,7 +130,7 @@ public class TargetClickboxOverlay extends Overlay break; case HULL: - Polygon objectClickbox = actor.getConvexHull(); + Shape objectClickbox = actor.getConvexHull(); if (objectClickbox == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/tarnslair/TarnsLairOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/tarnslair/TarnsLairOverlay.java index 01670c2fdf..8201c4270d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/tarnslair/TarnsLairOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/tarnslair/TarnsLairOverlay.java @@ -28,6 +28,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import javax.inject.Inject; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; @@ -69,11 +70,11 @@ public class TarnsLairOverlay extends Overlay { if (tile.getPlane() == client.getPlane() && obstacle.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { - Polygon p = tile.getGameObjects()[0].getConvexHull(); + Shape p = tile.getGameObjects()[0].getConvexHull(); if (p != null) { graphics.setColor(Color.GREEN); - graphics.drawPolygon(p); + graphics.draw(p); } } }); @@ -82,11 +83,11 @@ public class TarnsLairOverlay extends Overlay { if (tile.getPlane() == client.getPlane() && obstacle.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { - Polygon p = tile.getGameObjects()[0].getConvexHull(); + Shape p = tile.getGameObjects()[0].getConvexHull(); if (p != null) { graphics.setColor(Color.CYAN); - graphics.drawPolygon(p); + graphics.draw(p); } } }); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/theatre/rooms/nylocas/NyloHandler.java b/runelite-client/src/main/java/net/runelite/client/plugins/theatre/rooms/nylocas/NyloHandler.java index 278d5fc1e2..fb4c37d4c0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/theatre/rooms/nylocas/NyloHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/theatre/rooms/nylocas/NyloHandler.java @@ -5,6 +5,7 @@ import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -248,7 +249,7 @@ public class NyloHandler extends RoomHandler { try { - Polygon objectClickbox = npc.getConvexHull(); + Shape objectClickbox = npc.getConvexHull(); Color color; String name = npc.getName() != null ? npc.getName() : ""; @@ -266,7 +267,8 @@ public class NyloHandler extends RoomHandler color = Color.LIGHT_GRAY; } - renderPoly(graphics, color, objectClickbox); + graphics.setColor(color); + graphics.draw(objectClickbox); } catch (Exception ex) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index a46b0c07f5..c654c53429 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -141,6 +141,9 @@ public class WorldHopperPlugin extends Plugin @Inject private WorldHopperPingOverlay worldHopperOverlay; + @Inject + private WorldClient worldClient; + private ScheduledExecutorService hopperExecutorService; private NavigationButton navButton; @@ -533,7 +536,7 @@ public class WorldHopperPlugin extends Plugin { log.debug("Fetching worlds"); - new WorldClient().lookupWorlds() + worldClient.lookupWorlds() .subscribeOn(Schedulers.io()) .take(1) .subscribe( diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/zalcano/ZalcanoOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/zalcano/ZalcanoOverlay.java index b0c97ef371..2bb2c14120 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/zalcano/ZalcanoOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/zalcano/ZalcanoOverlay.java @@ -30,6 +30,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Shape; import java.util.List; import lombok.extern.slf4j.Slf4j; import net.runelite.api.AnimationID; @@ -174,7 +175,7 @@ public class ZalcanoOverlay extends Overlay { if (plugin.getGolem() != null) { - Polygon hull = plugin.getGolem().getConvexHull(); + Shape hull = plugin.getGolem().getConvexHull(); if (hull != null) { OverlayUtil.renderPolygon(graphics, hull, new Color(206, 41, 231)); @@ -187,7 +188,7 @@ public class ZalcanoOverlay extends Overlay { if (plugin.getZalcano() != null) { - Polygon hull = plugin.getZalcano().getConvexHull(); + Shape hull = plugin.getZalcano().getConvexHull(); if (hull != null) { OverlayUtil.renderPolygon(graphics, hull, config.zalcanoHullColor()); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java index 61c95ea785..4ea4c471ee 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayUtil.java @@ -33,8 +33,8 @@ import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; +import java.awt.Shape; import java.awt.Stroke; -import java.awt.geom.Area; import java.awt.image.BufferedImage; import java.util.List; import net.runelite.api.Actor; @@ -61,14 +61,14 @@ public class OverlayUtil private static final int MINIMAP_DOT_RADIUS = 4; private static final double UNIT = Math.PI / 1024.0d; - public static void renderPolygon(Graphics2D graphics, Polygon poly, Color color) + public static void renderPolygon(Graphics2D graphics, Shape poly, Color color) { graphics.setColor(color); final Stroke originalStroke = graphics.getStroke(); graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(poly); + graphics.draw(poly); graphics.setColor(new Color(0, 0, 0, 50)); - graphics.fillPolygon(poly); + graphics.fill(poly); graphics.setStroke(originalStroke); } @@ -211,7 +211,7 @@ public class OverlayUtil renderImageLocation(client, graphics, localLocation, image, 0); } - public static void renderHoverableArea(Graphics2D graphics, Area area, net.runelite.api.Point mousePosition, Color fillColor, Color borderColor, Color borderHoverColor) + public static void renderHoverableArea(Graphics2D graphics, Shape area, net.runelite.api.Point mousePosition, Color fillColor, Color borderColor, Color borderHoverColor) { if (area != null) { @@ -326,7 +326,7 @@ public class OverlayUtil } } - public static void renderClickBox(Graphics2D graphics, Point mousePosition, Area objectClickbox, Color configColor) + public static void renderClickBox(Graphics2D graphics, Point mousePosition, Shape objectClickbox, Color configColor) { if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY())) { diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java index 26f9b68998..aa3e32b502 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSBoundaryObjectMixin.java @@ -1,14 +1,17 @@ package net.runelite.mixins; -import net.runelite.api.Entity; +import java.awt.Polygon; +import java.awt.Shape; import net.runelite.api.Model; import net.runelite.api.Perspective; -import java.awt.geom.Area; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSBoundaryObject; import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSEntity; +import net.runelite.rs.api.RSModel; @Mixin(RSBoundaryObject.class) public abstract class RSBoundaryObjectMixin implements RSBoundaryObject @@ -34,9 +37,9 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject } @Inject - public Model getModelA() + public RSModel getModelA() { - Entity entity = getEntity1(); + RSEntity entity = getEntity1(); if (entity == null) { return null; @@ -44,7 +47,7 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject if (entity instanceof Model) { - return (Model) entity; + return (RSModel) entity; } else { @@ -53,9 +56,9 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject } @Inject - public Model getModelB() + public RSModel getModelB() { - Entity entity = getEntity2(); + RSEntity entity = getEntity2(); if (entity == null) { return null; @@ -63,7 +66,7 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject if (entity instanceof Model) { - return (Model) entity; + return (RSModel) entity; } else { @@ -73,12 +76,10 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject @Inject @Override - public Area getClickbox() + public Shape getClickbox() { - Area clickbox = new Area(); - - Area clickboxA = Perspective.getClickbox(client, getModelA(), 0, getLocalLocation()); - Area clickboxB = Perspective.getClickbox(client, getModelB(), 0, getLocalLocation()); + Shape clickboxA = Perspective.getClickbox(client, getModelA(), 0, getLocalLocation()); + Shape clickboxB = Perspective.getClickbox(client, getModelB(), 0, getLocalLocation()); if (clickboxA == null && clickboxB == null) { @@ -87,14 +88,26 @@ public abstract class RSBoundaryObjectMixin implements RSBoundaryObject if (clickboxA != null) { - clickbox.add(clickboxA); + return clickboxA; } - if (clickboxB != null) + return clickboxB; + } + + + + @Inject + @Override + public Polygon getConvexHull() + { + RSModel model = getModelA(); + + if (model == null) { - clickbox.add(clickboxB); + return null; } - return clickbox; + int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), 0, tileHeight); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java index 16245b40aa..f1149980ef 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSFloorDecorationMixin.java @@ -1,14 +1,17 @@ package net.runelite.mixins; +import java.awt.Polygon; +import java.awt.Shape; import net.runelite.api.Model; import net.runelite.api.Perspective; -import net.runelite.api.Entity; -import java.awt.geom.Area; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSEntity; import net.runelite.rs.api.RSFloorDecoration; +import net.runelite.rs.api.RSModel; @Mixin(RSFloorDecoration.class) public abstract class RSFloorDecorationMixin implements RSFloorDecoration @@ -35,9 +38,9 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration @Inject @Override - public Model getModel() + public RSModel getModel() { - Entity entity = getEntity(); + RSEntity entity = getEntity(); if (entity == null) { return null; @@ -45,7 +48,7 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration if (entity instanceof Model) { - return (Model) entity; + return (RSModel) entity; } else { @@ -53,9 +56,26 @@ public abstract class RSFloorDecorationMixin implements RSFloorDecoration } } + + @Inject @Override - public Area getClickbox() + public Polygon getConvexHull() + { + RSModel model = getModel(); + + if (model == null) + { + return null; + } + + int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); + return model.getConvexHull(getX(), getY(), 0, tileHeight); + } + + @Inject + @Override + public Shape getClickbox() { return Perspective.getClickbox(client, getModel(), 0, getLocalLocation()); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java index b0128249fb..1b34663f6f 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java @@ -25,10 +25,7 @@ package net.runelite.mixins; import java.awt.Polygon; -import java.awt.geom.Area; -import java.util.ArrayList; -import java.util.List; -import net.runelite.api.Model; +import java.awt.Shape; import net.runelite.api.Perspective; import net.runelite.api.Point; import net.runelite.api.coords.Angle; @@ -36,8 +33,6 @@ import net.runelite.api.coords.LocalPoint; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSEntity; import net.runelite.rs.api.RSGameObject; @@ -85,94 +80,11 @@ public abstract class RSGameObjectMixin implements RSGameObject @Inject @Override - public Area getClickbox() + public Shape getClickbox() { return Perspective.getClickbox(client, getModel(), getRsOrientation(), getLocalLocation()); } - @Inject - @Override - public Polygon[] getPolygons() - { - Model model = getModel(); - - if (model == null) - { - return null; - } - - int localX = getX(); - int localY = getY(); - - int orientation = getRsOrientation(); - - final int tileHeight = Perspective.getTileHeight(client, new LocalPoint(localX, localY), client.getPlane()); - - List triangles = model.getTriangles(); - - triangles = rotate(triangles, orientation); - - List polys = new ArrayList(); - for (Triangle triangle : triangles) - { - Vertex vx = triangle.getA(); - Vertex vy = triangle.getB(); - Vertex vz = triangle.getC(); - - Point x = Perspective.localToCanvas(client, - localX - vx.getX(), - localY - vx.getZ(), - tileHeight + vx.getY()); - - Point y = Perspective.localToCanvas(client, - localX - vy.getX(), - localY - vy.getZ(), - tileHeight + vy.getY()); - - Point z = Perspective.localToCanvas(client, - localX - vz.getX(), - localY - vz.getZ(), - tileHeight + vz.getY()); - - if (x == null || y == null || z == null) - { - return null; - } - - int[] xx = - { - x.getX(), y.getX(), z.getX() - }; - int[] yy = - { - x.getY(), y.getY(), z.getY() - }; - polys.add(new Polygon(xx, yy, 3)); - } - - return polys.toArray(new Polygon[0]); - } - - @Inject - private List rotate(List triangles, int orientation) - { - List rotatedTriangles = new ArrayList(); - for (Triangle triangle : triangles) - { - Vertex a = triangle.getA(); - Vertex b = triangle.getB(); - Vertex c = triangle.getC(); - - Triangle rotatedTriangle = new Triangle( - a.rotate(orientation), - b.rotate(orientation), - c.rotate(orientation) - ); - rotatedTriangles.add(rotatedTriangle); - } - return rotatedTriangles; - } - @Inject @Override public Polygon getConvexHull() diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java index c500884e73..7d04713ff3 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSPlayerMixin.java @@ -24,6 +24,9 @@ */ package net.runelite.mixins; +import java.awt.Polygon; +import java.util.ArrayList; +import java.util.List; import net.runelite.api.HeadIcon; import static net.runelite.api.HeadIcon.MAGIC; import static net.runelite.api.HeadIcon.MELEE; @@ -43,17 +46,14 @@ import static net.runelite.api.SkullIcon.DEAD_MAN_TWO; import static net.runelite.api.SkullIcon.SKULL; import static net.runelite.api.SkullIcon.SKULL_FIGHT_PIT; import net.runelite.api.coords.LocalPoint; -import net.runelite.api.mixins.MethodHook; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; -import java.awt.Polygon; -import java.util.ArrayList; -import java.util.List; import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.MethodHook; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; +import net.runelite.api.model.Triangle; +import net.runelite.api.model.Vertex; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSPlayer; @@ -169,28 +169,28 @@ public abstract class RSPlayerMixin implements RSPlayer System.err.println("vx: " + vx.getX() + " localX: " + localX); Point x = Perspective.localToCanvas(client, - localX - vx.getX(), - localY - vx.getZ(), - tileHeight + vx.getY()); + localX - vx.getX(), + localY - vx.getZ(), + tileHeight + vx.getY()); Point y = Perspective.localToCanvas(client, - localX - vy.getX(), - localY - vy.getZ(), - tileHeight + vy.getY()); + localX - vy.getX(), + localY - vy.getZ(), + tileHeight + vy.getY()); Point z = Perspective.localToCanvas(client, - localX - vz.getX(), - localY - vz.getZ(), - tileHeight + vz.getY()); + localX - vz.getX(), + localY - vz.getZ(), + tileHeight + vz.getY()); int[] xx = - { - x.getX(), y.getX(), z.getX() - }; + { + x.getX(), y.getX(), z.getX() + }; int[] yy = - { - x.getY(), y.getY(), z.getY() - }; + { + x.getY(), y.getY(), z.getY() + }; polys.add(new Polygon(xx, yy, 3)); } @@ -222,9 +222,9 @@ public abstract class RSPlayerMixin implements RSPlayer Vertex c = triangle.getC(); Triangle rotatedTriangle = new Triangle( - a.rotate(orientation), - b.rotate(orientation), - c.rotate(orientation) + a.rotate(orientation), + b.rotate(orientation), + c.rotate(orientation) ); rotatedTriangles.add(rotatedTriangle); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java index 2e36340a3a..c1395bc3c0 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallDecorationMixin.java @@ -1,10 +1,11 @@ package net.runelite.mixins; +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.Area; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.coords.LocalPoint; -import java.awt.Polygon; -import java.awt.geom.Area; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; @@ -86,14 +87,14 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration @Inject @Override - public Area getClickbox() + public Shape getClickbox() { Area clickbox = new Area(); LocalPoint lp = getLocalLocation(); - Area clickboxA = Perspective.getClickbox(client, getModel1(), 0, + Shape clickboxA = Perspective.getClickbox(client, getModel1(), 0, new LocalPoint(lp.getX() + getXOffset(), lp.getY() + getYOffset())); - Area clickboxB = Perspective.getClickbox(client, getModel2(), 0, lp); + Shape clickboxB = Perspective.getClickbox(client, getModel2(), 0, lp); if (clickboxA == null && clickboxB == null) { @@ -102,15 +103,10 @@ public abstract class RSWallDecorationMixin implements RSWallDecoration if (clickboxA != null) { - clickbox.add(clickboxA); + return clickboxA; } - if (clickboxB != null) - { - clickbox.add(clickboxB); - } - - return clickbox; + return clickboxB; } @Inject