// // $Id: GeomUtil.java,v 1.9 2004/08/27 02:12:35 mdb Exp $ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved // http://www.threerings.net/code/narya/ // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.geom; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Point2D; /** * General geometry utilites. */ public class GeomUtil { /** * Computes and returns the dot product of the two vectors. * * @param v1s the starting point of the first vector. * @param v1e the ending point of the first vector. * @param v2s the starting point of the second vector. * @param v2e the ending point of the second vector. */ public static int dot (Point v1s, Point v1e, Point v2s, Point v2e) { return ((v1e.x - v1s.x) * (v2e.x - v2s.x) + (v1e.y - v1s.y) * (v2e.y - v2s.y)); } /** * Computes and returns the dot product of the two vectors. See * {@link #dot(Point,Point,Point,Point)} for an explanation of the * arguments */ public static int dot (int v1sx, int v1sy, int v1ex, int v1ey, int v2sx, int v2sy, int v2ex, int v2ey) { return ((v1ex - v1sx) * (v2ex - v2sx) + (v1ey - v1sy) * (v2ey - v2sy)); } /** * Computes and returns the dot product of the two vectors. The * vectors are assumed to start with the same coordinate and end with * different coordinates. * * @param vs the starting point of both vectors. * @param v1e the ending point of the first vector. * @param v2e the ending point of the second vector. */ public static int dot (Point vs, Point v1e, Point v2e) { return ((v1e.x - vs.x) * (v2e.x - vs.x) + (v1e.y - vs.y) * (v2e.y - vs.y)); } /** * Computes and returns the dot product of the two vectors. * See {@link #dot(Point,Point,Point)} for an explanation of the * arguments */ public static int dot (int vsx, int vsy, int v1ex, int v1ey, int v2ex, int v2ey) { return ((v1ex - vsx) * (v2ex - vsx) + (v1ey - vsy) * (v2ey - vsy)); } /** * Computes the point nearest to the specified point p3 * on the line defined by the two points p1 and * p2. The computed point is stored into n. * Note: p1 and p2 must not be * coincident. * * @param p1 one point on the line. * @param p2 another point on the line (not equal to p1). * @param p3 the point to which we wish to be most near. * @param n the point on the line defined by p1 and * p2 that is nearest to p. * * @return the point object supplied via n. */ public static Point nearestToLine (Point p1, Point p2, Point p3, Point n) { // see http://astronomy.swin.edu.au/~pbourke/geometry/pointline/ // for a (not very good) explanation of the math int Ax = p2.x - p1.x, Ay = p2.y - p1.y; float u = (p3.x - p1.x) * Ax + (p3.y - p1.y) * Ay; u /= (Ax * Ax + Ay * Ay); n.x = p1.x + Math.round(Ax * u); n.y = p1.y + Math.round(Ay * u); return n; } /** * Calculate the intersection of two lines. Either line may be * considered as a line segment, and the intersecting point * is only considered valid if it lies upon the segment. * Note that Point extends Point2D. * * @param p1, p2 the coordinates of the first line. * @param seg1 if the first line should be considered a segment. * @param p3, p4 the coordinates of the second line. * @param seg2 if the second line should be considered a segment. * @param result the point that will be filled in with the intersecting * point. * * @return true if result was filled in, or false if the lines * are parallel or the point of intersection lies outside of a * segment. */ public static boolean lineIntersection ( Point2D p1, Point2D p2, boolean seg1, Point2D p3, Point2D p4, boolean seg2, Point2D result) { // see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ double y43 = p4.getY() - p3.getY(); double x21 = p2.getX() - p1.getX(); double x43 = p4.getX() - p3.getX(); double y21 = p2.getY() - p1.getY(); double denom = y43 * x21 - x43 * y21; if (denom == 0) { return false; } double y13 = p1.getY() - p3.getY(); double x13 = p1.getX() - p3.getX(); double ua = (x43 * y13 - y43 * x13) / denom; if (seg1 && ((ua < 0) || (ua > 1))) { return false; } if (seg2) { double ub = (x21 * y13 - y21 * x13) / denom; if ((ub < 0) || (ub > 1)) { return false; } } double x = p1.getX() + ua * x21; double y = p1.getY() + ua * y21; result.setLocation(x, y); return true; } /** * Returns less than zero if p2 is on the left hand side * of the line created by p1 and theta and * greater than zero if it is on the right hand side. In theory, it * will return zero if the point is on the line, but due to rounding * errors it almost always decides that it's not exactly on the line. * * @param p1 the point on the line whose side we're checking. * @param theta the (logical) angle defining the line. * @param p2 the point that lies on one side or the other of the line. */ public static int whichSide (Point p1, double theta, Point p2) { // obtain the point defining the right hand normal (N) theta += Math.PI/2; int x = p1.x + (int)Math.round(1000*Math.cos(theta)), y = p1.y + (int)Math.round(1000*Math.sin(theta)); // now dot the vector from p1->p2 with the vector from p1->N, if // it's positive, we're on the right hand side, if it's negative // we're on the left hand side and if it's zero, we're on the line return dot(p1.x, p1.y, p2.x, p2.y, x, y); } /** * Shifts the position of the tainer rectangle to ensure * that it contains the tained rectangle. The * tainer rectangle must be larger than or equal to the * size of the tained rectangle. */ public static void shiftToContain (Rectangle tainer, Rectangle tained) { if (tained.x < tainer.x) { tainer.x = tained.x; } if (tained.y < tainer.y) { tainer.y = tained.y; } if (tained.x + tained.width > tainer.x + tainer.width) { tainer.x = tained.x - (tainer.width - tained.width); } if (tained.y + tained.height > tainer.y + tainer.height) { tainer.y = tained.y - (tainer.height - tained.height); } } /** * Adds the target rectangle to the bounds of the source rectangle. If * the source rectangle is null, a new rectangle is created that is the * size of the target rectangle. * * @return the source rectangle. */ public static Rectangle grow (Rectangle source, Rectangle target) { if (target == null) { Log.warning("Can't grow with null rectangle [src=" + source + ", tgt=" + target + "]."); Thread.dumpStack(); } else if (source == null) { source = new Rectangle(target); } else { source.add(target); } return source; } /** * Returns the rectangle containing the specified tile in the supplied * larger rectangle. Tiles go from left to right, top to bottom. */ public static Rectangle getTile ( int width, int height, int tileWidth, int tileHeight, int tileIndex) { // figure out from whence to crop the tile int tilesPerRow = width / tileWidth; // if we got a bogus region, return bogus tile bounds if (tilesPerRow == 0) { return new Rectangle(0, 0, width, height); } int row = tileIndex / tilesPerRow; int col = tileIndex % tilesPerRow; // crop the tile-sized image chunk from the full image return new Rectangle( tileWidth*col, tileHeight*row, tileWidth, tileHeight); } }