package org.archstudio.bna.utils;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.archstudio.bna.IBNAModel;
import org.archstudio.bna.IBNAView;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.ICoordinateMapper;
import org.archstudio.bna.IThing;
import org.archstudio.bna.IThingPeer;
import org.archstudio.bna.facets.IHasAnchorPoint;
import org.archstudio.bna.facets.IHasBoundingBox;
import org.archstudio.bna.facets.IHasSelected;
import org.archstudio.bna.facets.IHasStickyShape;
import org.archstudio.bna.facets.IHasWorld;
import org.archstudio.bna.facets.peers.IHasInnerViewPeer;
import org.archstudio.swtutils.SWTWidgetUtils;
import org.archstudio.swtutils.constants.Orientation;
import org.archstudio.sysutils.ExpandableFloatBuffer;
import org.archstudio.sysutils.ExpandableIntBuffer;
import org.archstudio.sysutils.FastMap;
import org.archstudio.sysutils.Finally;
import org.archstudio.sysutils.SystemUtils;
import org.archstudio.sysutils.UIDGenerator;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Widget;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.CycleDetectingLockFactory;
import com.google.common.util.concurrent.CycleDetectingLockFactory.Policies;
public class BNAUtils {
private static final ReentrantReadWriteLock LOCK = CycleDetectingLockFactory.newInstance(Policies.DISABLED)
.newReentrantReadWriteLock(BNAUtils.class.getName(), true);
public static final double TO_POINTS_ERROR = 0.5d;
public static final double TO_POINTS_ERROR_SQ = TO_POINTS_ERROR * TO_POINTS_ERROR;
public static final double REAL_EQUAL_THRESHOLD = 0.0000001d;
public static final int LINE_CLICK_DISTANCE = 5;
@SuppressWarnings("unchecked")
public static final <T> T castOrNull(IThing thing, Class<T> thingClass) {
return thingClass.isInstance(thing) ? (T) thing : null;
}
private static final Finally UNLOCK = new Finally() {
@Override
public void close() {
LOCK.writeLock().unlock();
}
};
/**
* Obtains the BNA lock. The intended use is as follows:
* <code>
* try (Finally lock = BNAUtils.lock()) {
* // ... use BNA
* }
* </code>
* @return a closeable object capable of being used in a try-with-resources statement.
*/
public static Finally lock() {
LOCK.writeLock().lock();
return UNLOCK;
}
/**
* Asserts that the current thread has the lock. The lock is required for <b><i>any</i></b> BNA related method call.
* @see #lock() for an example of how to obtain the lock.
*/
public static void checkLock() {
if (!LOCK.writeLock().isHeldByCurrentThread()) {
throw new IllegalStateException("Thread does not hold current lock");
}
}
public static Rectangle normalizeRectangle(Rectangle rectangleResult) {
if (rectangleResult.width < 0) {
rectangleResult.x -= rectangleResult.width;
rectangleResult.width = -rectangleResult.width;
}
if (rectangleResult.height < 0) {
rectangleResult.y -= rectangleResult.height;
rectangleResult.height = -rectangleResult.height;
}
return rectangleResult;
}
public static String generateUID(String prefix) {
return UIDGenerator.generateUID(prefix);
}
public static boolean isWithin(Rectangle outsideRect, int x, int y) {
Rectangle out = normalizeRectangle(outsideRect);
int x1 = out.x;
int x2 = out.x + out.width;
int y1 = out.y;
int y2 = out.y + out.height;
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
}
public static boolean isWithin(Rectangle outsideRect, Rectangle insideRect) {
Rectangle in = normalizeRectangle(insideRect);
return isWithin(outsideRect, in.x, in.y) && isWithin(outsideRect, in.x + in.width, in.y)
&& isWithin(outsideRect, in.x, in.y + in.height)
&& isWithin(outsideRect, in.x + in.width, in.y + in.height);
}
public static boolean realEq(double v1, double v2) {
return Math.abs(v2 - v1) < REAL_EQUAL_THRESHOLD;
}
private static FastMap<Class<?>, Comparator<?>> likeHelpers = new FastMap<>(true);
static {
likeHelpers.put(Point2D.Float.class, new Comparator<Point2D>() {
@Override
public int compare(Point2D o1, Point2D o2) {
return realEq(o1.getX(), o2.getX()) && realEq(o1.getY(), o2.getY()) ? 0 : -1;
}
});
likeHelpers.put(Point2D.Double.class, new Comparator<Point2D>() {
@Override
public int compare(Point2D o1, Point2D o2) {
return realEq(o1.getX(), o2.getX()) && realEq(o1.getY(), o2.getY()) ? 0 : -1;
}
});
}
public static final boolean like(Object o1, Object o2) {
if (o1 == null || o2 == null) {
if (o1 != null || o2 != null) {
return false;
}
return true;
}
@SuppressWarnings("unchecked")
Comparator<Object> c = (Comparator<Object>) likeHelpers.get(o1.getClass());
if (c == null) {
return o1.equals(o2);
}
return c.compare(o1, o2) == 0;
}
public static final Point clone(Point p) {
return new Point(p.x, p.y);
}
public static final Rectangle clone(Rectangle r) {
return new Rectangle(r.x, r.y, r.width, r.height);
}
public static Dimension clone(Dimension d) {
return new Dimension(d.width, d.height);
}
public static final List<Point> worldToLocalPoint(final ICoordinateMapper cm, List<Point> worldPoints) {
return Lists.transform(worldPoints, new Function<Point, Point>() {
@Override
public Point apply(Point input) {
return cm.worldToLocal(new Point(input.x, input.y));
}
});
}
public static final List<Point2D> worldToLocalPoint2D(final ICoordinateMapper cm,
List<? extends Point2D> worldPoints) {
return Lists.transform(worldPoints, new Function<Point2D, Point2D>() {
@Override
public Point2D apply(Point2D input) {
return cm.worldToLocal(input);
}
});
}
public static Dimension worldToLocalCornerSize(ICoordinateMapper cm, Rectangle worldRectangle,
int worldCornerWidth, int worldCornerHeight) {
Rectangle cornerWorldRect = new Rectangle(worldRectangle.x, worldRectangle.y, Math.min(worldCornerWidth,
worldRectangle.width), Math.min(worldCornerHeight, worldRectangle.height));
Rectangle localCornerWorldRect = cm.worldToLocal(cornerWorldRect);
return new Dimension(localCornerWorldRect.width, localCornerWorldRect.height);
}
public static final List<Point> localToWorld(ICoordinateMapper cm, List<Point> localPointsResult) {
for (Point p : localPointsResult) {
cm.localToWorld(p);
}
return localPointsResult;
}
public static boolean wasControlPressed(MouseEvent evt) {
return (evt.stateMask & SWT.CONTROL) != 0;
}
public static boolean wasShiftPressed(MouseEvent evt) {
return (evt.stateMask & SWT.SHIFT) != 0;
}
public static boolean wasClick(MouseEvent downEvt, MouseEvent upEvt) {
if (downEvt.button == upEvt.button) {
int dx = upEvt.x - downEvt.x;
int dy = upEvt.y - downEvt.y;
if (dx == 0 && dy == 0) {
return true;
}
}
return false;
}
public static void setRectangle(Rectangle r, int x, int y, int width, int height) {
r.x = x;
r.y = y;
r.width = width;
r.height = height;
}
public static Rectangle createAlignedRectangle(Point p, int width, int height, Orientation o) {
Rectangle r = new Rectangle(0, 0, 0, 0);
alignRectangle(r, p, width, height, o);
return r;
}
public static void alignRectangle(Rectangle r, Point p, int width, int height, Orientation o) {
switch (o) {
case NONE:
setRectangle(r, p.x - width / 2, p.y - height / 2, width, height);
break;
case NORTHWEST:
setRectangle(r, p.x - width, p.y - height, width, height);
break;
case NORTH:
setRectangle(r, p.x - width / 2, p.y - height, width, height);
break;
case NORTHEAST:
setRectangle(r, p.x, p.y - height, width, height);
break;
case EAST:
setRectangle(r, p.x, p.y - height / 2, width, height);
break;
case SOUTHEAST:
setRectangle(r, p.x, p.y, width, height);
break;
case SOUTH:
setRectangle(r, p.x - width / 2, p.y, width, height);
break;
case SOUTHWEST:
setRectangle(r, p.x - width, p.y, width, height);
break;
case WEST:
setRectangle(r, p.x - width, p.y - height / 2, width, height);
break;
}
}
private static float[] deg2rad = null;
public static float degreesToRadians(int degrees) {
while (degrees < 0) {
degrees += 360;
}
degrees = degrees % 360;
if (deg2rad == null) {
deg2rad = new float[360];
for (int i = 0; i < 360; i++) {
deg2rad[i] = i * (float) Math.PI / 180f;
}
}
return deg2rad[degrees];
}
public static Point[] toPointArray(int[] points) {
Point[] pa = new Point[points.length / 2];
for (int i = 0; i < points.length; i += 2) {
pa[i / 2] = new Point(points[i], points[i + 1]);
}
return pa;
}
public static int[] createIsocolesTriangle(Rectangle sbb, Orientation facing) {
int ft = 6;
int fb = 16;
int x1 = sbb.x;
int y1 = sbb.y;
int xm = sbb.x + sbb.width / 2;
int ym = sbb.y + sbb.height / 2;
int xq = x1 + sbb.width * ft / fb;
int yq = y1 + sbb.height * ft / fb;
int xqg = x1 + sbb.width - sbb.width * ft / fb;
int yqg = y1 + sbb.height - sbb.height * ft / fb;
int x2 = x1 + sbb.width;
int y2 = y1 + sbb.height;
int px1, px2, px3;
int py1, py2, py3;
switch (facing) {
case NORTHWEST:
px1 = xq;
py1 = y2;
px2 = x1;
py2 = y1;
px3 = x2;
py3 = yq;
break;
case NORTH:
px1 = x1;
py1 = y2;
px2 = xm;
py2 = y1;
px3 = x2;
py3 = y2;
break;
case NORTHEAST:
px1 = x1;
py1 = yq;
px2 = x2;
py2 = y1;
px3 = xqg;
py3 = y2;
break;
case EAST:
px1 = x1;
py1 = y1;
px2 = x2;
py2 = ym;
px3 = x1;
py3 = y2;
break;
case SOUTHEAST:
px1 = xqg;
py1 = y1;
px2 = x2;
py2 = y2;
px3 = x1;
py3 = yqg;
break;
case SOUTH:
px1 = x1;
py1 = y1;
px2 = xm;
py2 = y2;
px3 = x2;
py3 = y1;
break;
case SOUTHWEST:
px1 = xq;
py1 = y1;
px2 = x1;
py2 = y2;
px3 = x2;
py3 = yqg;
break;
case WEST:
px1 = x2;
py1 = y1;
px2 = x1;
py2 = ym;
px3 = x2;
py3 = y2;
break;
default:
throw new IllegalArgumentException("Invalid facing");
}
return new int[] { px1, py1, px2, py2, px3, py3 };
}
public static Rectangle insetRectangle(Rectangle r, Rectangle insets) {
Rectangle i = new Rectangle(r.x + insets.x, r.y + insets.y, r.width + insets.width, r.height + insets.height);
if (i.width < 0) {
return null;
}
if (i.height < 0) {
return null;
}
if (!r.contains(i.x, i.y)) {
return null;
}
return i;
}
public static boolean isEdgePoint(Point p, Rectangle r) {
int x2 = r.x + r.width;
int y2 = r.y + r.height;
if (p.x == r.x && p.y >= r.y && p.y <= y2) {
// It's on the left rail
return true;
}
if (p.x == x2 && p.y >= r.y && p.y <= y2) {
// It's on the right rail
return true;
}
if (p.y == r.y && p.x >= r.x && p.x <= x2) {
// it's on the top rail
return true;
}
if (p.y == y2 && p.x >= r.x && p.x <= x2) {
// it's on the bottom rail
return true;
}
return false;
}
public static Orientation getOrientationOfEdgePoint(Point p, Rectangle r) {
int x2 = r.x + r.width;
int y2 = r.y + r.height;
if (p.x == r.x && p.y == r.y) {
return Orientation.NORTHWEST;
}
else if (p.x == r.x && p.y == y2) {
return Orientation.SOUTHWEST;
}
else if (p.x == x2 && p.y == r.y) {
return Orientation.NORTHEAST;
}
else if (p.x == x2 && p.y == y2) {
return Orientation.SOUTHEAST;
}
else if (p.x == r.x && p.y >= r.y && p.y <= y2) {
return Orientation.WEST;
}
if (p.x == x2 && p.y >= r.y && p.y <= y2) {
return Orientation.EAST;
}
if (p.y == r.y && p.x >= r.x && p.x <= x2) {
return Orientation.NORTH;
}
if (p.y == y2 && p.x >= r.x && p.x <= x2) {
return Orientation.SOUTH;
}
// it's not on a rail
return Orientation.NONE;
}
public static Point findClosestEdgePoint(Point p, Rectangle r) {
Point np = new Point(p.x, p.y);
boolean midx = false;
boolean midy = false;
if (p.x < r.x) {
// It's to the left of the rectangle
np.x = r.x;
}
else if (p.x < r.x + r.width) {
// It's in the middle of the rectangle
midx = true;
}
else {
// It's beyond the right of the rectangle
np.x = r.x + r.width;
}
if (p.y < r.y) {
np.y = r.y;
}
else if (p.y < r.y + r.height) {
midy = true;
}
else {
np.y = r.y + r.height;
}
if (midx && midy) {
// It was within the rectangle
int dl = Math.abs(p.x - r.x);
int dr = Math.abs(p.x - (r.x + r.width));
int dt = Math.abs(p.y - r.y);
int db = Math.abs(p.y - (r.y + r.height));
if (dt <= db && dt <= dl && dt <= dr) {
// it's closest to the top rail.
np.y = r.y;
return np;
}
else if (db <= dt && db <= dl && db <= dr) {
// it's closest to the bottom rail
np.y = r.y + r.height;
return np;
}
else if (dl <= dt && dl <= db && dl <= dr) {
// it's closest to the left rail
np.x = r.x;
return np;
}
else {
np.x = r.x + r.width;
return np;
}
}
return np;
}
public static Point scaleAndMoveBorderPoint(Point p, Rectangle oldRect, Rectangle newRect) {
if (oldRect == null || newRect == null) {
return new Point(p.x, p.y);
}
//int ox1 = oldRect.x;
//int ox2 = oldRect.x + oldRect.width;
//int oy1 = oldRect.y;
//int oy2 = oldRect.y + oldRect.height;
int ow = oldRect.width;
int oh = oldRect.height;
//int nx1 = newRect.x;
//int nx2 = newRect.x + newRect.width;
//int ny1 = newRect.y;
//int ny2 = newRect.y + newRect.height;
int nw = newRect.width;
int nh = newRect.height;
int dw = nw - ow;
int dh = nh - oh;
double sx = (double) nw / (double) ow;
double sy = (double) nh / (double) oh;
//int dx = nx1 - ox1;
//int dy = ny1 - oy1;
Point p2 = new Point(p.x, p.y);
if (p.y == oldRect.y) {
// It's on the top rail
// Keep it on the top rail
p2.y = newRect.y;
// Old distance from the left rail
int dist = p.x - oldRect.x;
if (dw != 0) {
// Scale that distance
dist = SystemUtils.round(dist * sx);
}
// Also perform translation
p2.x = newRect.x + dist;
}
else if (p.y == oldRect.y + oldRect.height // - 1
|| p.y == oldRect.y + oldRect.height) {
// It's on the bottom rail
// Keep it on the bottom rail
p2.y = newRect.y + newRect.height/* - 1 */;
// Old distance from the left rail
int dist = p.x - oldRect.x;
if (dw != 0) {
// Scale that distance
dist = SystemUtils.round(dist * sx);
}
// Also perform translation
p2.x = newRect.x + dist;
}
else if (p.x == oldRect.x) {
// It's on the left rail
// Keep it on the left rail
p2.x = newRect.x;
// Old distance from the top rail
int dist = p.y - oldRect.y;
if (dh != 0) {
// Scale that distance
dist = SystemUtils.round(dist * sy);
}
// Also perform translation
p2.y = newRect.y + dist;
}
else if (p.x == oldRect.x + oldRect.width // - 1
|| p.x == oldRect.x + oldRect.width) {
// It's on the right rail
// Keep it on the right rail
p2.x = newRect.x + newRect.width/* - 1 */;
// Old distance from the top rail
int dist = p.y - oldRect.y;
if (dh != 0) {
// Scale that distance
dist = SystemUtils.round(dist * sy);
}
// Also perform translation
p2.y = newRect.y + dist;
}
// Normalize
if (p2.x < newRect.x) {
p2.x = newRect.x;
}
if (p2.x >= newRect.x + newRect.width) {
p2.x = newRect.x + newRect.width/* - 1 */;
}
if (p2.y < newRect.y) {
p2.y = newRect.y;
}
if (p2.y >= newRect.y + newRect.height) {
p2.y = newRect.y + newRect.height/* - 1 */;
}
return p2;
}
public static RGB getRGBForSystemColor(Device d, int systemColorID) {
Color c = d.getSystemColor(systemColorID);
if (c == null) {
return null;
}
return c.getRGB();
}
public static boolean isPointOnRectangle(Point p, Rectangle r) {
return isPointOnRectangle(p.x, p.y, r.x, r.y, r.width, r.height);
}
public static boolean isPointOnRectangle(int x, int y, int rx, int ry, int rw, int rh) {
if (x == rx || x == rx + rw) {
if (y >= ry && y <= ry + rh) {
return true;
}
}
if (y == ry || y == ry + rh) {
if (x >= rx && x <= rx + rw) {
return true;
}
}
return false;
}
public static class PointToRectangleDistanceData {
public Orientation closestSide;
public double dist;
}
public static PointToRectangleDistanceData getPointToRectangleDistance(Point p, Rectangle r) {
double closestDist = Double.MAX_VALUE;
Orientation closestSide = Orientation.NONE;
double dist;
// Check north distance
dist = Line2D.ptSegDist(r.x, r.y, r.x + r.width, r.y, p.x, p.y);
if (dist < closestDist) {
closestDist = dist;
closestSide = Orientation.NORTH;
}
dist = Line2D.ptSegDist(r.x, r.y, r.x, r.y + r.height, p.x, p.y);
if (dist < closestDist) {
closestDist = dist;
closestSide = Orientation.WEST;
}
dist = Line2D.ptSegDist(r.x + r.width, r.y, r.x + r.width, r.y + r.height, p.x, p.y);
if (dist < closestDist) {
closestDist = dist;
closestSide = Orientation.EAST;
}
dist = Line2D.ptSegDist(r.x, r.y + r.height, r.x + r.width, r.y + r.height, p.x, p.y);
if (dist < closestDist) {
closestDist = dist;
closestSide = Orientation.SOUTH;
}
PointToRectangleDistanceData dd = new PointToRectangleDistanceData();
dd.closestSide = closestSide;
dd.dist = closestDist;
return dd;
}
// public static Rectangle clone(Rectangle r) {
// return r == null ? null : new Rectangle(r.x, r.y, r.width, r.height);
// }
//
// public static final Point clone(Point p) {
// return p == null ? null : new Point(p.x, p.y);
// }
//
// public static final Point[] clone(Point[] points) {
// if (points == null) {
// return null;
// }
// Point[] newPoints = new Point[points.length];
// for (int i = 0; i < points.length; i++) {
// newPoints[i] = clone(points[i]);
// }
// return newPoints;
// }
/**
* @deprecated Use {@link SWTWidgetUtils#async(Widget,Runnable)} instead
*/
@Deprecated
public static void asyncExec(final Widget w, final Runnable r) {
SWTWidgetUtils.async(w, r);
}
/**
* @deprecated Use {@link SWTWidgetUtils#async(Display,Runnable)} instead
*/
@Deprecated
public static void asyncExec(final Display d, final Runnable r) {
SWTWidgetUtils.async(d, r);
}
/**
* @deprecated Use {@link SWTWidgetUtils#sync(Widget,Runnable)} instead
*/
@Deprecated
public static void syncExec(final Widget w, final Runnable r) {
SWTWidgetUtils.sync(w, r);
}
/**
* @deprecated Use {@link SWTWidgetUtils#sync(Display,Runnable)} instead
*/
@Deprecated
public static void syncExec(final Display d, final Runnable r) {
SWTWidgetUtils.sync(d, r);
}
// public static Widget getParentsComposite(IBNAView view) {
// if (view == null) {
// return null;
// }
// Composite c = view.getParentComposite();
// if (c != null) {
// return c;
// }
// return getParentComposite(view.getParentView());
// }
public static @Nullable
Point2D getCentralPoint(IThing t) {
if (t instanceof IHasBoundingBox) {
Rectangle r = ((IHasBoundingBox) t).getBoundingBox();
return new Point2D.Double(r.x + r.width / 2, r.y + r.height / 2);
}
if (t instanceof IHasAnchorPoint) {
return ((IHasAnchorPoint) t).getAnchorPoint();
}
if (t instanceof IHasStickyShape) {
java.awt.Rectangle r = ((IHasStickyShape) t).getStickyShape().getBounds();
return new Point2D.Double(r.getCenterX(), r.getCenterY());
}
return null;
}
private static final Predicate<IThing> isSelectedPredicate = new Predicate<IThing>() {
@Override
public boolean apply(IThing t) {
if (t instanceof IHasSelected) {
return ((IHasSelected) t).isSelected();
}
return false;
};
};
public static final Collection<IThing> getSelectedThings(IBNAModel m) {
return Collections2.filter(m.getAllThings(), isSelectedPredicate);
}
public static final int sizeOfSelectedThings(IBNAModel m) {
return Iterables.size(getSelectedThings(m));
}
public static boolean infinitelyRecurses(IBNAView view) {
IBNAWorld world = view.getBNAWorld();
if (world == null) {
return false;
}
IBNAView view2 = view.getParentView();
while (view2 != null) {
if (world.equals(view2.getBNAWorld())) {
return true;
}
view2 = view2.getParentView();
}
return false;
}
public static final Dimension getSize(Rectangle r) {
return new Dimension(r.width, r.height);
}
public static double getDistance(Point p1, Point p2) {
if (p1 != null && p2 != null) {
double dx = p2.x - p1.x;
double dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
return Double.POSITIVE_INFINITY;
}
public static double getDistance(Point2D p1, Point2D p2) {
if (p1 != null && p2 != null) {
return p1.distance(p2);
}
return Double.POSITIVE_INFINITY;
}
public static Point2D getClosestPointOnShape(Shape shape, double nearX, double nearY, double refX, double refY) {
// search for the point closest to (nearX,nearY) that intersects the referenceLine
Line2D referenceLine = new Line2D.Double(refX, refY, nearX, nearY);
double[] coords = new double[6];
double closestDistanceSq = Double.POSITIVE_INFINITY;
Point2D closestIntersection = new Point2D.Double(refX, refY);
{
Point2D closePoint = new Point2D.Double();
Point2D lastPoint = new Point2D.Double();
Line2D lineSegment = new Line2D.Double();
for (PathIterator i = shape.getPathIterator(new AffineTransform(), 0.5f); !i.isDone(); i.next()) {
switch (i.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
closePoint.setLocation(coords[0], coords[1]);
lastPoint.setLocation(coords[0], coords[1]);
continue;
case PathIterator.SEG_CLOSE:
lineSegment.setLine(closePoint.getX(), closePoint.getY(), coords[0], coords[1]);
lastPoint.setLocation(coords[0], coords[1]);
break;
case PathIterator.SEG_LINETO:
lineSegment.setLine(lastPoint.getX(), lastPoint.getY(), coords[0], coords[1]);
lastPoint.setLocation(coords[0], coords[1]);
break;
default:
throw new UnsupportedOperationException();
}
Point2D intersection = getLineIntersection(lineSegment, referenceLine);
if (lineSegment.ptSegDistSq(intersection) < REAL_EQUAL_THRESHOLD) {
double distanceSq = intersection.distanceSq(nearX, nearY);
if (distanceSq < closestDistanceSq) {
closestDistanceSq = distanceSq;
closestIntersection = intersection;
}
}
}
}
return closestIntersection;
}
private static Point2D.Double getLineIntersection(Line2D l, Line2D r) {
// see: http://en.wikipedia.org/wiki/Line-line_intersection
double x1 = l.getX1();
double y1 = l.getY1();
double x2 = l.getX2();
double y2 = l.getY2();
double x3 = r.getX1();
double y3 = r.getY1();
double x4 = r.getX2();
double y4 = r.getY2();
double x1y2 = x1 * y2;
double y1x2 = y1 * x2;
double x3y4 = x3 * y4;
double y3x4 = y3 * x4;
double xNumerator = (x1y2 - y1x2) * (x3 - x4) - (x1 - x2) * (x3y4 - y3x4);
double yNumerator = (x1y2 - y1x2) * (y3 - y4) - (y1 - y2) * (x3y4 - y3x4);
double denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
return new Point2D.Double(xNumerator / denominator, yNumerator / denominator);
}
private static Point2D getClosestPointOnLineSegment(Line2D l, double x3, double y3) {
// see: http://paulbourke.net/geometry/pointline/
double x1 = l.getX1();
double y1 = l.getY1();
double x2 = l.getX2();
double y2 = l.getY2();
double u = ((x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1)) / l.getP2().distanceSq(l.getP1());
double x = x1 + u * (x2 - x1);
double y = y1 + u * (y2 - y1);
Point2D p1 = l.getP1();
Point2D p2 = l.getP2();
// determine if the calculated intersection is between the segment points
if (l.ptSegDistSq(x, y) < REAL_EQUAL_THRESHOLD) {
return new Point2D.Double(x, y);
}
// its not, so use the closest end point
double dp1 = p1.distanceSq(x3, y3);
double dp2 = p2.distanceSq(x3, y3);
Point2D p = dp1 < dp2 ? p1 : p2;
return p;
}
public static Point2D getClosestPointOnShape(Shape shape, double nearX, double nearY) {
// search for the closest line segment
double[] pathCoords = new double[6];
double closestDistanceSq = Double.POSITIVE_INFINITY;
Line2D closestLineSeg = new Line2D.Double();
Point2D lastPoint = new Point2D.Double(0, 0);
for (PathIterator i = shape.getPathIterator(new AffineTransform(), 0.5f); !i.isDone(); i.next()) {
Line2D lineSegment = new Line2D.Double();
switch (i.currentSegment(pathCoords)) {
case PathIterator.SEG_MOVETO:
lastPoint.setLocation(pathCoords[0], pathCoords[1]);
continue;
case PathIterator.SEG_CLOSE:
continue;
case PathIterator.SEG_LINETO:
lineSegment.setLine(lastPoint.getX(), lastPoint.getY(), pathCoords[0], pathCoords[1]);
lastPoint.setLocation(pathCoords[0], pathCoords[1]);
break;
default:
throw new UnsupportedOperationException();
}
double distanceSq = lineSegment.ptSegDistSq(nearX, nearY);
if (distanceSq < closestDistanceSq) {
closestDistanceSq = distanceSq;
closestLineSeg.setLine(lineSegment);
}
}
return getClosestPointOnLineSegment(closestLineSeg, nearX, nearY);
}
public static final IBNAView getInternalView(IBNAView outerView, IThing worldThing) {
if (worldThing instanceof IHasWorld) {
IThingPeer<?> worldThingPeer = outerView.getThingPeer(worldThing);
if (worldThingPeer instanceof IHasInnerViewPeer) {
return ((IHasInnerViewPeer<?>) worldThingPeer).getInnerView();
}
}
return null;
}
public static final IBNAView getInternalView(IBNAView outerView, Object worldThingID) {
return getInternalView(outerView, outerView.getBNAWorld().getBNAModel().getThing(worldThingID));
}
public static final void expandRectangle(Rectangle r, Point toIncludePoint) {
if (toIncludePoint.x < r.x) {
r.width += r.x - toIncludePoint.x;
r.x = toIncludePoint.x;
}
else if (toIncludePoint.x > r.x + r.width) {
r.width = toIncludePoint.x - r.x;
}
if (toIncludePoint.y < r.y) {
r.height += r.y - toIncludePoint.y;
r.y = toIncludePoint.y;
}
else if (toIncludePoint.y > r.y + r.height) {
r.height = toIncludePoint.y - r.y;
}
}
public static int[] toXYIntArrayFromPoint2D(List<Point2D> points) {
int[] xyArray = new int[2 * points.size()];
for (int i = 0, length = points.size(), xy = 0; i < length; i++) {
Point2D p = points.get(i);
xyArray[xy++] = SystemUtils.round(p.getX());
xyArray[xy++] = SystemUtils.round(p.getY());
}
return xyArray;
}
public static int[] toXYIntArrayFromPoint(List<Point> points) {
int[] xyArray = new int[2 * points.size()];
for (int i = 0, length = points.size(), xy = 0; i < length; i++) {
Point p = points.get(i);
xyArray[xy++] = p.x;
xyArray[xy++] = p.y;
}
return xyArray;
}
public static int[] toXYIntArrayFromPoint(ICoordinateMapper cm, List<Point> points, Point anchorPoint) {
int[] xyArray = new int[2 * points.size()];
for (int i = 0, length = points.size(), xy = 0; i < length; i++) {
Point p = points.get(i);
p.x += anchorPoint.x;
p.y += anchorPoint.y;
p = cm.worldToLocal(p);
xyArray[xy++] = p.x;
xyArray[xy++] = p.y;
}
return xyArray;
}
private static final Function<IThing, Object> thingToIDFunction = new Function<IThing, Object>() {
@Override
public Object apply(IThing input) {
return input.getID();
}
};
public static final List<Object> getThingIDs(Iterable<? extends IThing> things) {
return Lists.newArrayList(Iterables.transform(things, thingToIDFunction));
}
public static final void union(Rectangle result, Rectangle other) {
if (result.isEmpty()) {
result.x = other.x;
result.y = other.y;
result.width = other.width;
result.height = other.height;
}
else if (!other.isEmpty()) {
result.union(other);
}
}
public static RGB adjustBrightness(RGB rgb, float factor) {
float r = rgb.red * factor;
float g = rgb.green * factor;
float b = rgb.blue * factor;
return new RGB(//
SystemUtils.bound(0, SystemUtils.round(r), 255),//
SystemUtils.bound(0, SystemUtils.round(g), 255),//
SystemUtils.bound(0, SystemUtils.round(b), 255));
}
public static final Rectangle toRectangle(java.awt.Rectangle bounds) {
return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
}
public static final Rectangle toRectangle(java.awt.geom.Rectangle2D bounds) {
int x1 = SystemUtils.floor(bounds.getMinX());
int y1 = SystemUtils.floor(bounds.getMinY());
int x2 = SystemUtils.ceil(bounds.getMaxX());
int y2 = SystemUtils.ceil(bounds.getMaxY());
return new Rectangle(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
}
public static final Rectangle2D toRectangle2D(Rectangle bounds) {
return new Rectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height);
}
public static Point toPoint(double x, double y) {
return new Point(SystemUtils.round(x), SystemUtils.round(y));
}
public static Point toPoint(java.awt.Point point) {
return new Point(point.x, point.y);
}
public static Point toPoint(Point2D p) {
return new Point(SystemUtils.round(p.getX()), SystemUtils.round(p.getY()));
}
public static final Point2D toPoint2D(Point p) {
return new Point2D.Float(p.x, p.y);
}
public static Dimension toDimension(Point p) {
return new Dimension(p.x, p.y);
}
public static Dimension toDimension(Rectangle2D r) {
return new Dimension(SystemUtils.round(r.getWidth()), SystemUtils.round(r.getHeight()));
}
public static Dimension toDimension(Rectangle lbb) {
return new Dimension(lbb.width, lbb.height);
}
public static void toXYFloatBuffer(Shape localShape, ExpandableFloatBuffer xyFloatBuffer) {
float firstX = 0, firstY = 0;
float lastX = 0, lastY = 0;
PathIterator p = localShape.getPathIterator(null, TO_POINTS_ERROR);
double[] coords = new double[6];
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
if (Math.abs(firstX - lastX) > TO_POINTS_ERROR || Math.abs(firstY - lastY) > TO_POINTS_ERROR) {
xyFloatBuffer.put(lastX = firstX);
xyFloatBuffer.put(lastY = firstY);
}
break;
case PathIterator.SEG_MOVETO:
xyFloatBuffer.put(firstX = lastX = (float) coords[0]);
xyFloatBuffer.put(firstY = lastY = (float) coords[1]);
break;
case PathIterator.SEG_LINETO:
float nextX = (float) coords[0];
float nextY = (float) coords[1];
if (Math.abs(nextX - lastX) > TO_POINTS_ERROR || Math.abs(nextY - lastY) > TO_POINTS_ERROR) {
xyFloatBuffer.put(lastX = nextX);
xyFloatBuffer.put(lastY = nextY);
}
break;
default:
throw new IllegalArgumentException();
}
p.next();
}
}
public static void toXYIntBuffer(Shape localShape, ExpandableIntBuffer xyIntBuffer) {
int firstX = 0, firstY = 0;
int lastX = 0, lastY = 0;
PathIterator p = localShape.getPathIterator(null, TO_POINTS_ERROR);
double[] coords = new double[6];
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
if (Math.abs(firstX - lastX) > TO_POINTS_ERROR || Math.abs(firstY - lastY) > TO_POINTS_ERROR) {
xyIntBuffer.put(lastX = firstX);
xyIntBuffer.put(lastY = firstY);
}
break;
case PathIterator.SEG_MOVETO:
xyIntBuffer.put(firstX = lastX = SystemUtils.round(coords[0]));
xyIntBuffer.put(firstY = lastY = SystemUtils.round(coords[1]));
break;
case PathIterator.SEG_LINETO:
int nextX = SystemUtils.round(coords[0]);
int nextY = SystemUtils.round(coords[1]);
if (Math.abs(nextX - lastX) > TO_POINTS_ERROR || Math.abs(nextY - lastY) > TO_POINTS_ERROR) {
xyIntBuffer.put(lastX = nextX);
xyIntBuffer.put(lastY = nextY);
}
break;
default:
throw new IllegalArgumentException();
}
p.next();
}
}
public static final Path toPath(Path path, Shape localShape) {
PathIterator p = localShape.getPathIterator(null);
double[] coords = new double[6];
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
path.close();
break;
case PathIterator.SEG_MOVETO:
path.moveTo((float) coords[0], (float) coords[1]);
break;
case PathIterator.SEG_LINETO:
path.lineTo((float) coords[0], (float) coords[1]);
break;
case PathIterator.SEG_QUADTO:
path.quadTo((float) coords[0], (float) coords[1], (float) coords[2], (float) coords[3]);
break;
case PathIterator.SEG_CUBICTO:
path.cubicTo((float) coords[0], (float) coords[1], (float) coords[2], (float) coords[3],
(float) coords[4], (float) coords[5]);
break;
default:
throw new IllegalArgumentException();
}
p.next();
}
return path;
}
public static Path2D worldToLocal(ICoordinateMapper cm, Shape shape) {
Path2D p = new Path2D.Double();
PathIterator i = shape.getPathIterator(null);
double[] coords = new double[6];
Point2D.Double d = new Point2D.Double();
Point2D d2;
while (!i.isDone()) {
switch (i.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
d.x = coords[0];
d.y = coords[1];
d2 = cm.worldToLocal(d);
p.moveTo(d2.getX(), d2.getY());
break;
case PathIterator.SEG_LINETO:
d.x = coords[0];
d.y = coords[1];
d2 = cm.worldToLocal(d);
p.lineTo(d2.getX(), d2.getY());
break;
case PathIterator.SEG_QUADTO:
for (int j = 0; j < 4; j += 2) {
d.x = coords[j];
d.y = coords[j + 1];
d2 = cm.worldToLocal(d);
coords[j] = d2.getX();
coords[j + 1] = d2.getY();
}
p.quadTo(coords[0], coords[1], coords[2], coords[3]);
break;
case PathIterator.SEG_CUBICTO:
for (int j = 0; j < 6; j += 2) {
d.x = coords[j];
d.y = coords[j + 1];
d2 = cm.worldToLocal(d);
coords[j] = d2.getX();
coords[j + 1] = d2.getY();
}
p.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
break;
case PathIterator.SEG_CLOSE:
p.closePath();
break;
default:
throw new IllegalArgumentException();
}
i.next();
}
return p;
}
}