Files
runelite/runelite-api/src/main/java/net/runelite/api/Perspective.java
Adam 3d79ab60a2 perspective: add gpu projection for modelToCanvas
GPU uses an alternative projection to avoid vertex snapping. Usually
this doesn't matter much, and in the worst case causes the discrepancy
to be only a few pixels, but with the model outline feature it causes
the outlines to be noticibly off when GPU is enabled.

This adds a second model-to-canvas method using the alternative
projection and uses it when GPU is enabled.
2021-09-22 15:01:41 -04:00

888 lines
25 KiB
Java

/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
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.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
/**
* A utility class containing methods to help with conversion between
* in-game features to canvas areas.
*/
public class Perspective
{
public static final double UNIT = Math.PI / 1024d; // How much of the circle each unit of SINE/COSINE is
public static final int LOCAL_COORD_BITS = 7;
public static final int LOCAL_TILE_SIZE = 1 << LOCAL_COORD_BITS; // 128 - size of a tile in local coordinates
public static final int LOCAL_HALF_TILE_SIZE = LOCAL_TILE_SIZE / 2;
public static final int SCENE_SIZE = Constants.SCENE_SIZE; // in tiles
public static final int[] SINE = new int[2048]; // sine angles for each of the 2048 units, * 65536 and stored as an int
public static final int[] COSINE = new int[2048]; // cosine
static
{
for (int i = 0; i < 2048; ++i)
{
SINE[i] = (int) (65536.0D * Math.sin((double) i * UNIT));
COSINE[i] = (int) (65536.0D * Math.cos((double) i * UNIT));
}
}
/**
* Translates two-dimensional ground coordinates within the 3D world to
* their corresponding coordinates on the game screen.
*
* @param client the game client
* @param point ground coordinate
* @param plane ground plane on the z axis
* @return a {@link Point} on screen corresponding to the position in
* 3D-space
*/
@Nullable
public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint point, int plane)
{
return localToCanvas(client, point, plane, 0);
}
/**
* Translates two-dimensional ground coordinates within the 3D world to
* their corresponding coordinates on the game screen.
*
* @param client the game client
* @param point ground coordinate
* @param plane ground plane on the z axis
* @param zOffset distance from ground on the z axis
* @return a {@link Point} on screen corresponding to the position in
* 3D-space
*/
@Nullable
public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint point, int plane, int zOffset)
{
final int tileHeight = getTileHeight(client, point, plane);
return localToCanvas(client, point.getX(), point.getY(), tileHeight - zOffset);
}
/**
* Translates three-dimensional local coordinates within the 3D world to
* their corresponding coordinates on the game screen.
*
* @param client the game client
* @param x ground coordinate on the x axis
* @param y ground coordinate on the y axis
* @param z
* @return a {@link Point} on screen corresponding to the position in
* 3D-space
*/
public static Point localToCanvas(@Nonnull Client client, int x, int y, int z)
{
if (x >= 128 && y >= 128 && x <= 13056 && y <= 13056)
{
x -= client.getCameraX();
y -= client.getCameraY();
z -= client.getCameraZ();
int cameraPitch = client.getCameraPitch();
int cameraYaw = client.getCameraYaw();
int pitchSin = SINE[cameraPitch];
int pitchCos = COSINE[cameraPitch];
int yawSin = SINE[cameraYaw];
int yawCos = COSINE[cameraYaw];
int var8 = yawCos * x + y * yawSin >> 16;
y = yawCos * y - yawSin * x >> 16;
x = var8;
var8 = pitchCos * z - y * pitchSin >> 16;
y = z * pitchSin + y * pitchCos >> 16;
if (y >= 50)
{
int pointX = client.getViewportWidth() / 2 + x * client.getScale() / y;
int pointY = client.getViewportHeight() / 2 + var8 * client.getScale() / y;
return new Point(
pointX + client.getViewportXOffset(),
pointY + client.getViewportYOffset());
}
}
return null;
}
/**
* Translates a model's vertices into 2d space. There is a separate implementation for GPU since GPU
* uses a slightly more precise projection that can cause features like model outlines being noticeably
* off otherwise.
*/
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)
{
if (client.isGpu())
{
modelToCanvasGpu(client, end, x3dCenter, y3dCenter, z3dCenter, rotate, x3d, y3d, z3d, x2d, y2d);
}
else
{
modelToCanvasCpu(client, end, x3dCenter, y3dCenter, z3dCenter, rotate, x3d, y3d, z3d, x2d, y2d);
}
}
private static void modelToCanvasGpu(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();
final float
pitchSin = SINE[cameraPitch] / 65536.0f,
pitchCos = COSINE[cameraPitch] / 65536.0f,
yawSin = SINE[cameraYaw] / 65536.0f,
yawCos = COSINE[cameraYaw] / 65536.0f,
rotateSin = SINE[rotate] / 65536.0f,
rotateCos = COSINE[rotate] / 65536.0f,
cx = x3dCenter - client.getCameraX(),
cy = y3dCenter - client.getCameraY(),
cz = z3dCenter - client.getCameraZ(),
viewportXMiddle = client.getViewportWidth() / 2f,
viewportYMiddle = client.getViewportHeight() / 2f,
viewportXOffset = client.getViewportXOffset(),
viewportYOffset = client.getViewportYOffset(),
zoom3d = client.getScale();
for (int i = 0; i < end; i++)
{
float x = x3d[i];
float y = y3d[i];
float z = z3d[i];
if (rotate != 0)
{
float x0 = x;
x = x0 * rotateCos + y * rotateSin;
y = y * rotateCos - x0 * rotateSin;
}
x += cx;
y += cy;
z += cz;
final float
x1 = x * yawCos + y * yawSin,
y1 = y * yawCos - x * yawSin,
y2 = z * pitchCos - y1 * pitchSin,
z1 = y1 * pitchCos + z * pitchSin;
int viewX, viewY;
if (z1 < 50)
{
viewX = Integer.MIN_VALUE;
viewY = Integer.MIN_VALUE;
}
else
{
viewX = Math.round((viewportXMiddle + x1 * zoom3d / z1) + viewportXOffset);
viewY = Math.round((viewportYMiddle + y2 * zoom3d / z1) + viewportYOffset);
}
x2d[i] = viewX;
y2d[i] = viewY;
}
}
private static void modelToCanvasCpu(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;
}
}
/**
* Translates two-dimensional ground coordinates within the 3D world to
* their corresponding coordinates on the Minimap.
*
* @param client the game client
* @param point ground coordinate
* @return a {@link Point} on screen corresponding to the position in
* 3D-space
*/
@Nullable
public static Point localToMinimap(@Nonnull Client client, @Nonnull LocalPoint point)
{
return localToMinimap(client, point, 6400);
}
/**
* Translates two-dimensional ground coordinates within the 3D world to
* their corresponding coordinates on the Minimap.
*
* @param client the game client
* @param point ground coordinate
* @param distance max distance from local player to minimap point
* @return a {@link Point} on screen corresponding to the position in
* 3D-space
*/
@Nullable
public static Point localToMinimap(@Nonnull Client client, @Nonnull LocalPoint point, int distance)
{
LocalPoint localLocation = client.getLocalPlayer().getLocalLocation();
int x = point.getX() / 32 - localLocation.getX() / 32;
int y = point.getY() / 32 - localLocation.getY() / 32;
int dist = x * x + y * y;
if (dist < distance)
{
Widget minimapDrawWidget;
if (client.isResized())
{
if (client.getVar(Varbits.SIDE_PANELS) == 1)
{
minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_DRAW_AREA);
}
else
{
minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_STONES_DRAW_AREA);
}
}
else
{
minimapDrawWidget = client.getWidget(WidgetInfo.FIXED_VIEWPORT_MINIMAP_DRAW_AREA);
}
if (minimapDrawWidget == null || minimapDrawWidget.isHidden())
{
return null;
}
final int angle = client.getMapAngle() & 0x7FF;
final int sin = SINE[angle];
final int cos = COSINE[angle];
final int xx = y * sin + cos * x >> 16;
final int yy = sin * x - y * cos >> 16;
Point loc = minimapDrawWidget.getCanvasLocation();
int miniMapX = loc.getX() + xx + minimapDrawWidget.getWidth() / 2;
int miniMapY = minimapDrawWidget.getHeight() / 2 + loc.getY() + yy;
return new Point(miniMapX, miniMapY);
}
return null;
}
/**
* Calculates the above ground height of a tile point.
*
* @param client the game client
* @param point the local ground coordinate
* @param plane the client plane/ground level
* @return the offset from the ground of the tile
*/
public static int getTileHeight(@Nonnull Client client, @Nonnull LocalPoint point, int plane)
{
int sceneX = point.getSceneX();
int sceneY = point.getSceneY();
if (sceneX >= 0 && sceneY >= 0 && sceneX < SCENE_SIZE && sceneY < SCENE_SIZE)
{
byte[][][] tileSettings = client.getTileSettings();
int[][][] tileHeights = client.getTileHeights();
int z1 = plane;
if (plane < Constants.MAX_Z - 1 && (tileSettings[1][sceneX][sceneY] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE)
{
z1 = plane + 1;
}
int x = point.getX() & (LOCAL_TILE_SIZE - 1);
int y = point.getY() & (LOCAL_TILE_SIZE - 1);
int var8 = x * tileHeights[z1][sceneX + 1][sceneY] + (LOCAL_TILE_SIZE - x) * tileHeights[z1][sceneX][sceneY] >> LOCAL_COORD_BITS;
int var9 = tileHeights[z1][sceneX][sceneY + 1] * (LOCAL_TILE_SIZE - x) + x * tileHeights[z1][sceneX + 1][sceneY + 1] >> LOCAL_COORD_BITS;
return (LOCAL_TILE_SIZE - y) * var8 + y * var9 >> LOCAL_COORD_BITS;
}
return 0;
}
/**
* Get the height of a location, in local coordinates. Interpolates the height from the adjacent tiles.
* Does not account for bridges.
* @param client
* @param localX
* @param localY
* @param plane
* @return
*/
private static int getHeight(@Nonnull Client client, int localX, int localY, int plane)
{
int sceneX = localX >> LOCAL_COORD_BITS;
int sceneY = localY >> LOCAL_COORD_BITS;
if (sceneX >= 0 && sceneY >= 0 && sceneX < SCENE_SIZE && sceneY < SCENE_SIZE)
{
int[][][] tileHeights = client.getTileHeights();
int x = localX & (LOCAL_TILE_SIZE - 1);
int y = localY & (LOCAL_TILE_SIZE - 1);
int var8 = x * tileHeights[plane][sceneX + 1][sceneY] + (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneX][sceneY] >> LOCAL_COORD_BITS;
int var9 = tileHeights[plane][sceneX][sceneY + 1] * (LOCAL_TILE_SIZE - x) + x * tileHeights[plane][sceneX + 1][sceneY + 1] >> LOCAL_COORD_BITS;
return (LOCAL_TILE_SIZE - y) * var8 + y * var9 >> LOCAL_COORD_BITS;
}
return 0;
}
/**
* Calculates a tile polygon from offset worldToScreen() points.
*
* @param client the game client
* @param localLocation local location of the tile
* @return a {@link Polygon} on screen corresponding to the given
* localLocation.
*/
public static Polygon getCanvasTilePoly(@Nonnull Client client, @Nonnull LocalPoint localLocation)
{
return getCanvasTileAreaPoly(client, localLocation, 1);
}
/**
* Calculates a tile polygon from offset worldToScreen() points.
*
* @param client the game client
* @param localLocation local location of the tile
* @param zOffset offset from ground plane
* @return a {@link Polygon} on screen corresponding to the given
* localLocation.
*/
public static Polygon getCanvasTilePoly(@Nonnull Client client, @Nonnull LocalPoint localLocation, int zOffset)
{
return getCanvasTileAreaPoly(client, localLocation, 1, 1, client.getPlane(), zOffset);
}
/**
* Returns a polygon representing an area.
*
* @param client the game client
* @param localLocation the center location of the AoE
* @param size the size of the area (ie. 3x3 AoE evaluates to size 3)
* @return a polygon representing the tiles in the area
*/
public static Polygon getCanvasTileAreaPoly(@Nonnull Client client, @Nonnull LocalPoint localLocation, int size)
{
return getCanvasTileAreaPoly(client, localLocation, size, size, client.getPlane(), 0);
}
/**
* Returns a polygon representing an area.
*
* @param client the game client
* @param localLocation the center location of the AoE
* @param sizeX the size of the area in tiles on the x axis
* @param sizeY the size of the area in tiles on the y axis
* @param plane the plane of the area
* @param zOffset offset from ground plane
* @return a polygon representing the tiles in the area
*/
public static Polygon getCanvasTileAreaPoly(
@Nonnull Client client,
@Nonnull LocalPoint localLocation,
int sizeX,
int sizeY,
int plane,
int zOffset)
{
if (!localLocation.isInScene())
{
return null;
}
final byte[][][] tileSettings = client.getTileSettings();
final int sceneX = localLocation.getSceneX();
final int sceneY = localLocation.getSceneY();
int tilePlane = plane;
if (plane < Constants.MAX_Z - 1 && (tileSettings[1][sceneX][sceneY] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE)
{
tilePlane = plane + 1;
}
final int swX = localLocation.getX() - (sizeX * LOCAL_TILE_SIZE / 2);
final int swY = localLocation.getY() - (sizeY * LOCAL_TILE_SIZE / 2);
final int neX = localLocation.getX() + (sizeX * LOCAL_TILE_SIZE / 2);
final int neY = localLocation.getY() + (sizeY * LOCAL_TILE_SIZE / 2);
final int seX = swX;
final int seY = neY;
final int nwX = neX;
final int nwY = swY;
final int swHeight = getHeight(client, swX, swY, tilePlane) - zOffset;
final int nwHeight = getHeight(client, nwX, nwY, tilePlane) - zOffset;
final int neHeight = getHeight(client, neX, neY, tilePlane) - zOffset;
final int seHeight = getHeight(client, seX, seY, tilePlane) - zOffset;
Point p1 = localToCanvas(client, swX, swY, swHeight);
Point p2 = localToCanvas(client, nwX, nwY, nwHeight);
Point p3 = localToCanvas(client, neX, neY, neHeight);
Point p4 = localToCanvas(client, seX, seY, seHeight);
if (p1 == null || p2 == null || p3 == null || p4 == null)
{
return null;
}
Polygon poly = new Polygon();
poly.addPoint(p1.getX(), p1.getY());
poly.addPoint(p2.getX(), p2.getY());
poly.addPoint(p3.getX(), p3.getY());
poly.addPoint(p4.getX(), p4.getY());
return poly;
}
/**
* Calculates text position and centers depending on string length.
*
* @param client the game client
* @param graphics the game graphics
* @param localLocation local location of the tile
* @param text string for width measurement
* @param zOffset offset from ground plane
* @return a {@link Point} on screen corresponding to the given
* localLocation.
*/
public static Point getCanvasTextLocation(
@Nonnull Client client,
@Nonnull Graphics2D graphics,
@Nonnull LocalPoint localLocation,
@Nullable String text,
int zOffset)
{
if (text == null)
{
return null;
}
int plane = client.getPlane();
Point p = localToCanvas(client, localLocation, plane, zOffset);
if (p == null)
{
return null;
}
FontMetrics fm = graphics.getFontMetrics();
Rectangle2D bounds = fm.getStringBounds(text, graphics);
int xOffset = p.getX() - (int) (bounds.getWidth() / 2);
return new Point(xOffset, p.getY());
}
/**
* Calculates image position and centers depending on image size.
*
* @param client the game client
* @param localLocation local location of the tile
* @param image image for size measurement
* @param zOffset offset from ground plane
* @return a {@link Point} on screen corresponding to the given
* localLocation.
*/
public static Point getCanvasImageLocation(
@Nonnull Client client,
@Nonnull LocalPoint localLocation,
@Nonnull BufferedImage image,
int zOffset)
{
int plane = client.getPlane();
Point p = localToCanvas(client, localLocation, plane, zOffset);
if (p == null)
{
return null;
}
int xOffset = p.getX() - image.getWidth() / 2;
int yOffset = p.getY() - image.getHeight() / 2;
return new Point(xOffset, yOffset);
}
/**
* Calculates image position and centers depending on image size.
*
* @param client the game client
* @param localLocation local location of the tile
* @param image image for size measurement
* @return a {@link Point} on screen corresponding to the given
* localLocation.
*/
public static Point getMiniMapImageLocation(
@Nonnull Client client,
@Nonnull LocalPoint localLocation,
@Nonnull BufferedImage image)
{
Point p = Perspective.localToMinimap(client, localLocation);
if (p == null)
{
return null;
}
int xOffset = p.getX() - image.getWidth() / 2;
int yOffset = p.getY() - image.getHeight() / 2;
return new Point(xOffset, yOffset);
}
/**
* Calculates sprite position and centers depending on sprite size.
*
* @param client the game client
* @param localLocation local location of the tile
* @param sprite SpritePixel for size measurement
* @param zOffset offset from ground plane
* @return a {@link Point} on screen corresponding to the given localLocation.
*/
public static Point getCanvasSpriteLocation(
@Nonnull Client client,
@Nonnull LocalPoint localLocation,
@Nonnull SpritePixels sprite,
int zOffset)
{
int plane = client.getPlane();
Point p = localToCanvas(client, localLocation, plane, zOffset);
if (p == null)
{
return null;
}
int xOffset = p.getX() - sprite.getWidth() / 2;
int yOffset = p.getY() - sprite.getHeight() / 2;
return new Point(xOffset, yOffset);
}
/**
* You don't want this. Use {@link TileObject#getClickbox()} instead.
* <p>
* 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 orientation the orientation of the model (0-2048, where 0 is north)
* @param point the coordinate of the tile
* @return the clickable area of the model
*/
@Nullable
public static Shape getClickbox(@Nonnull Client client, Model model, int orientation, LocalPoint point)
{
if (model == null)
{
return null;
}
int x = point.getX();
int y = point.getY();
int z = getTileHeight(client, point, client.getPlane());
SimplePolygon bounds = calculateAABB(client, model, orientation, x, y, z);
if (bounds == null)
{
return null;
}
if (model.isClickable())
{
return bounds;
}
Shapes<SimplePolygon> bounds2d = calculate2DBounds(client, model, orientation, x, y, z);
if (bounds2d == null)
{
return null;
}
for (SimplePolygon poly : bounds2d.getShapes())
{
poly.intersectWithConvex(bounds);
}
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<SimplePolygon> 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()];
final int[] faceColors3 = m.getFaceColors3();
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<RectangleUnion.Rectangle> rects = new ArrayList<>(m.getTrianglesCount());
nextTri:
for (int tri = 0; tri < m.getTrianglesCount(); tri++)
{
if (faceColors3[tri] == -2)
{
continue;
}
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);
}
/**
* Calculates text position and centers on minimap depending on string length.
*
* @param client the game client
* @param graphics the game graphics
* @param localLocation local location of the tile
* @param text string for width measurement
* @return a {@link Point} on screen corresponding to the given
* localLocation.
*/
public static Point getCanvasTextMiniMapLocation(
@Nonnull Client client,
@Nonnull Graphics2D graphics,
@Nonnull LocalPoint localLocation,
@Nonnull String text)
{
Point p = Perspective.localToMinimap(client, localLocation);
if (p == null)
{
return null;
}
FontMetrics fm = graphics.getFontMetrics();
Rectangle2D bounds = fm.getStringBounds(text, graphics);
int xOffset = p.getX() - (int) (bounds.getWidth() / 2);
int yOffset = p.getY() - (int) (bounds.getHeight() / 2) + fm.getAscent();
return new Point(xOffset, yOffset);
}
}