runelite-api: Add simple Shape implementations

This commit is contained in:
Max Weber
2019-09-11 22:20:28 -06:00
parent a4cfbc3b42
commit 293b9cdf7c
2 changed files with 708 additions and 0 deletions

View File

@@ -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<T extends Shape> implements Shape
{
public Shapes(T ...shape)
{
this(Arrays.asList(shape));
}
@Getter
private final List<T> 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<PathIterator> iter;
private PathIterator current = null;
private final int windingRule;
ShapeIterator(Iterator<PathIterator> 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);
}
}
}

View File

@@ -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<Point> toRuneLitePointList()
{
List<Point> 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;
}
}
}