Java tutorial
/* * (C) 2004 - Geotechnical Software Services * * This code 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 code 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 program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ //package no.geosoft.cc.geometry; /** * A <em>Region</em> is simply an area, as the name implies, and is * implemented as a so called "y-x-banded" array of rectangles; Each Region * is made up of a certain number of rectangles sorted by y coordinate first, * and then by x coordinate. * <p> * Furthermore, the rectangles are banded such that every rectangle with a * given upper-left y coordinate (y1) will have the same lower-right y * coordinate (y2) and vice versa. If a rectangle has scanlines in a band, * it will span the entire vertical distance of the band. This means that * some areas that could be merged into a taller rectangle will be represented * as several shorter rectangles to account for shorter rectangles to its * left or right but within its "vertical scope". * <p> * An added constraint on the rectangles is that they must cover as much * horizontal area as possible. E.g. no two rectangles in a band are allowed * to touch. Whenever possible, bands will be merged together to cover a * greater vertical distance (and thus reduce the number of rectangles). * Two bands can be merged only if the bottom of one touches the top of the * other and they have rectangles in the same places (of the same width, of * course). This maintains the y-x-banding. * <p> * Region operations includes add (union), subtract, intersect, and * exclusive-or. * <p> * This class corresponds to Region.c of the X11 distribution and the * implemntation is based on it. * <p> * The <em>Region</em> is essentially equivalent to an AWT <em>Area</em> * but with different back-end implementation. Becnhmarking proves it more * than 100 times faster than AWT Area for binary CAG operations, * <p> * Thanks to: * <ul> * <li>Bryan Lin @ China Minmetals Corporation - for identifying * synchronization errors when run on the MS WindowsXP platform. * <li>Maxim Butov @ Belhard - for identifying error in the * isInside(Rect) method. * </ul> * * @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a> */ public class Region implements Cloneable { private static final int OPERATION_UNION = 0; private static final int OPERATION_INTERSECTION = 1; private static final int OPERATION_SUBTRACTION = 2; private static final int OPERATION_XOR = 3; private static final int INITIAL_SIZE = 40; // 10 rectangles // Temporary working area common for all regions for maximum performance private static int gRectangles_[] = new int[INITIAL_SIZE]; private static int gNRectangles_ = 0; private static boolean isLocked_ = false; private Box extent_; private int rectangles_[]; // y0,y1,x0,x1,..... private int nRectangles_; /** * Create an empty region. Corresponds to XCreateRegion of X11. */ public Region() { extent_ = new Box(0, 0, 0, 0); rectangles_ = new int[INITIAL_SIZE]; nRectangles_ = 0; } /** * Create the region covering the (possibly complex) polygon specified * by the x and y parameters. * Corresponds to XPolygonRegion of X11. * * @param x X values of polygon vertices. * @param y Y values of polygon vertices. */ public Region(int x[], int y[]) { // TODO. See PolyReg.c of X11. } /** * Create a region constituting of a single rectangle as specified. * * @param rectangle Rectangle to create region from. */ public Region(Rect rectangle) { this(); set(rectangle); } /** * Create a region consisting of one rectangle as specified. * * @param x X position of upper left corner of rectangle. * @param y Y position of upper left corner of rectangle. * @param width Width of rectangle. * @param height Height of rectangle. */ public Region(int x, int y, int width, int height) { this(new Rect(x, y, width, height)); } /** * Create a region consisting of one rectangle as specified. * * @param box Box specification of rectangle to create region from. */ public Region(Box box) { this(new Rect(box)); } /** * Create a region as a copy of the specified region. * * @param region Region to copy. */ public Region(Region region) { extent_ = new Box(); rectangles_ = new int[region.nRectangles_ << 2]; set(region); } /** * Clone this region. * * @return Clone of this region. */ public Object clone() { return new Region(this); } /** * Convert this region to an AWT Area. * <p> * Note: The AWT classes are referenced explicitly here rather tham * importing them to indicate that the Region implementation does not * dependent on the AWT. * * @return Area equivalent of this rectangle. */ public java.awt.geom.Area createArea() { if (nRectangles_ == 0) return null; java.awt.Rectangle rectangle = new java.awt.Rectangle(rectangles_[2], rectangles_[0], rectangles_[3] - rectangles_[2], rectangles_[1] - rectangles_[0]); java.awt.geom.Area area = new java.awt.geom.Area(rectangle); for (int i = 1; i < nRectangles_; i++) { int j = i * 4; rectangle = new java.awt.Rectangle(rectangles_[j + 2], rectangles_[j + 0], rectangles_[j + 3] - rectangles_[j + 2], rectangles_[j + 1] - rectangles_[j + 0]); area.add(new java.awt.geom.Area(rectangle)); } return area; } private static void checkMemory(Region region, int nRectangles) { int nEntries = nRectangles << 2; if (region == null) { if (gRectangles_.length < nEntries) { int newSize = nEntries * 2; int newArray[] = new int[newSize]; System.arraycopy(gRectangles_, 0, newArray, 0, gRectangles_.length); gRectangles_ = newArray; } } else { if (region.rectangles_.length < nEntries) { int newSize = nEntries * 2; int newArray[] = new int[newSize]; System.arraycopy(region.rectangles_, 0, newArray, 0, region.rectangles_.length); region.rectangles_ = newArray; } } } /** * Set the content of this region according to the specified * region. * * @param region Region to copy. */ public void set(Region region) { extent_.copy(region.extent_); checkMemory(this, region.nRectangles_); System.arraycopy(region.rectangles_, 0, rectangles_, 0, region.nRectangles_ << 2); nRectangles_ = region.nRectangles_; } /** * Set the content of this region according to the specified * rectangle. * * @param rectangle Rectangle to set region according to. */ public void set(Rect rectangle) { rectangles_ = new int[INITIAL_SIZE]; if (rectangle.isEmpty()) { extent_ = new Box(); nRectangles_ = 0; } else { extent_ = new Box(rectangle); rectangles_[0] = extent_.y1; rectangles_[1] = extent_.y2; rectangles_[2] = extent_.x1; rectangles_[3] = extent_.x2; nRectangles_ = 1; } } /** * Clear the region. */ public void clear() { nRectangles_ = 0; extent_.set(0, 0, 0, 0); } /** * Return true if this region equals the specified object. * Corrensponds to XEqualRegion of X11. * * @param object Object to check against. * @return True if the two regions equals, false otherwise. * @throws ClassCastException if object is not of type Region. */ public boolean equals(Object object) { Region region = (Region) object; if (nRectangles_ != region.nRectangles_) return false; else if (nRectangles_ == 0) return true; else if (extent_.x1 != region.extent_.x1) return false; else if (extent_.x2 != region.extent_.x2) return false; else if (extent_.y1 != region.extent_.y1) return false; else if (extent_.y2 != region.extent_.y2) return false; else { for (int i = 0; i < nRectangles_ << 2; i++) if (rectangles_[i] != region.rectangles_[i]) return false; } return true; } /** * Return true if the region is empty. Corresponds to XEmptyRegion in X11. * * @return True if the region is empty, false otherwise. */ public boolean isEmpty() { return nRectangles_ == 0; } /** * Offset the entire region a specified distance. * Corresponds to XOffsetRegion in X11. * * @param dx Offset in x direction. * @param dy Offset in y direction. */ public void offset(int dx, int dy) { for (int i = 0; i < rectangles_.length; i += 4) { rectangles_[i + 0] += dy; rectangles_[i + 1] += dy; rectangles_[i + 2] += dx; rectangles_[i + 3] += dx; } extent_.offset(dx, dy); } /** * Return true if the specified region intersect this region. * * @param region Region to check against. * @return True if the region intersects this one, false otherwise. */ public boolean isIntersecting(Region region) { Region r = (Region) clone(); r.intersect(region); return !r.isEmpty(); } /** * Return true if the specified rectangle intersect this region. * * @param rectangle Rectangle to check against. * @return True if the rectangle intersects this, false otherwise. */ public boolean isIntersecting(Rect rectangle) { Region region = new Region(rectangle); return isIntersecting(region); } /** * Return true if the specified point is inside this region. * This method corresponds to XPointInRegion in X11. * * @param x X part of point to check. * @param y Y part of point to check. * @return True if the point is inside the region, false otherwise. */ public boolean isInside(int x, int y) { if (isEmpty()) return false; if (!extent_.isInside(x, y)) return false; int rEnd = nRectangles_ << 2; // Find correct band int i = 0; while (i < rEnd && rectangles_[i + 1] < y) { if (rectangles_[i] > y) return false; // Passed the band i += 4; } // Check each rectangle in the band while (i < rEnd && rectangles_[i] <= y) { if (x >= rectangles_[i + 2] && x < rectangles_[i + 3]) return true; i += 4; } return false; } /** * Return true if the specified rectangle is inside this region. * This method corresponds to XRectInRegion in X11. * * @param rectangle Rectangle to check. * @return True if the rectangle is inside this region, * false otherwise. */ public boolean isInside(Rect rectangle) { // Trivial reject case 1 if (isEmpty() || rectangle.isEmpty()) return false; // Trivial reject case 2 if (!extent_.isOverlapping(rectangle)) return false; int x1 = rectangle.x; int x2 = rectangle.x + rectangle.width; int y1 = rectangle.y; int y2 = rectangle.y + rectangle.height; int rEnd = nRectangles_ << 2; // Trivial reject case 3 if (rectangles_[0] > y1) return false; // Loop to start band int i = 0; while (i < rEnd && rectangles_[i + 1] <= y1) { i += 4; if (rectangles_[i] > y1) return false; } while (i < rEnd) { int yTop = rectangles_[i]; int yBottom = rectangles_[i + 1]; // Find start rectangle within band while (i < rEnd && rectangles_[i + 3] <= x1) { i += 4; if (rectangles_[i] > yTop) return false; // Passed the band } if (i == rEnd) return false; // This rectangle must cover the entire rectangle horizontally if (x1 < rectangles_[i + 2] || x2 > rectangles_[i + 3]) return false; // See if we are done if (rectangles_[i + 1] >= y2) return true; // Move to next band i += 4; while (i < rEnd && rectangles_[i] == yTop) i += 4; if (i == rEnd) return false; if (rectangles_[i] > yBottom) return false; } return false; } /** * Return true if this region is inside of the specified rectangle. * * @param rectangle Rectangle to check if this is inside of. * @return True if this region is inside the specified rectangle, * false otherwise. */ public boolean isInsideOf(Rect rectangle) { return subtract(this, rectangle).isEmpty(); } /** * Return the extent of the region. * Correspond to XClipBox in X11. * * @return The extent of this region. */ public Rect getExtent() { return new Rect(extent_); } /** * Return the number of rectangles in the region. In case the number * is getting very high, the application might choose to call collapse(). * * @return Number of rectangles this region consists of. */ public int getNRectangles() { return nRectangles_; } /** * Collapse the region into its extent box. Useful if the region becomes * very complex (number of rectangles is getting high) and the client * accepts the (in general) coarser result region. */ public void collapse() { rectangles_[0] = extent_.y1; rectangles_[1] = extent_.y2; rectangles_[2] = extent_.x1; rectangles_[3] = extent_.x2; nRectangles_ = 1; } /** * Perform a logical set operation between this and the specified * region. Corresponds to miRegionOp in Region.c of X11. * * @param region Region to combine with. * @param operationType Combination operator. */ private void combine(Region region, int operationType) { // This is the only method (with sub methods) that utilize the // common working area gRectangles_. The lock ensures that only // one thread access this variable at any time. while (isLocked_) ; isLocked_ = true; int r1 = 0; int r2 = 0; int r1End = nRectangles_ << 2; int r2End = region.nRectangles_ << 2; // Initialize the working region gNRectangles_ = 0; int yTop = 0; int yBottom = extent_.y1 < region.extent_.y1 ? extent_.y1 : region.extent_.y1; int previousBand = 0; int currentBand; int r1BandEnd, r2BandEnd; int top, bottom; // Main loop do { currentBand = gNRectangles_; // Find end of the current r1 band r1BandEnd = r1 + 4; while (r1BandEnd != r1End && rectangles_[r1BandEnd] == rectangles_[r1]) r1BandEnd += 4; // Find end of the current r2 band r2BandEnd = r2 + 4; while (r2BandEnd != r2End && region.rectangles_[r2BandEnd] == region.rectangles_[r2]) r2BandEnd += 4; // First handle non-intersection band if any if (rectangles_[r1] < region.rectangles_[r2]) { top = Math.max(rectangles_[r1], yBottom); bottom = Math.min(rectangles_[r1 + 1], region.rectangles_[r2]); if (top != bottom) nonOverlap1(rectangles_, r1, r1BandEnd, top, bottom, operationType); yTop = region.rectangles_[r2]; } else if (region.rectangles_[r2] < rectangles_[r1]) { top = Math.max(region.rectangles_[r2], yBottom); bottom = Math.min(region.rectangles_[r2 + 1], rectangles_[r1]); if (top != bottom) nonOverlap2(region.rectangles_, r2, r2BandEnd, top, bottom, operationType); yTop = rectangles_[r1]; } else yTop = rectangles_[r1]; // Then coalesce if possible if (gNRectangles_ != currentBand) previousBand = coalesceBands(previousBand, currentBand); currentBand = gNRectangles_; // Check if this is an intersecting band yBottom = Math.min(rectangles_[r1 + 1], region.rectangles_[r2 + 1]); if (yBottom > yTop) overlap(rectangles_, r1, r1BandEnd, region.rectangles_, r2, r2BandEnd, yTop, yBottom, operationType); // Coalesce again if (gNRectangles_ != currentBand) previousBand = coalesceBands(previousBand, currentBand); // If we're done with a band, skip forward in the region to the next band if (rectangles_[r1 + 1] == yBottom) r1 = r1BandEnd; if (region.rectangles_[r2 + 1] == yBottom) r2 = r2BandEnd; } while (r1 != r1End && r2 != r2End); currentBand = gNRectangles_; // // Deal with whichever region still has rectangles left // if (r1 != r1End) { do { r1BandEnd = r1; while (r1BandEnd < r1End && rectangles_[r1BandEnd] == rectangles_[r1]) r1BandEnd += 4; top = Math.max(rectangles_[r1], yBottom); bottom = rectangles_[r1 + 1]; nonOverlap1(rectangles_, r1, r1BandEnd, top, bottom, operationType); r1 = r1BandEnd; } while (r1 != r1End); } else if (r2 != r2End) { do { r2BandEnd = r2; while (r2BandEnd < r2End && region.rectangles_[r2BandEnd] == region.rectangles_[r2]) r2BandEnd += 4; top = Math.max(region.rectangles_[r2], yBottom); bottom = region.rectangles_[r2 + 1]; nonOverlap2(region.rectangles_, r2, r2BandEnd, top, bottom, operationType); r2 = r2BandEnd; } while (r2 != r2End); } // Coalesce again if (currentBand != gNRectangles_) coalesceBands(previousBand, currentBand); // Copy the work region into this checkMemory(this, gNRectangles_); System.arraycopy(gRectangles_, 0, rectangles_, 0, gNRectangles_ << 2); nRectangles_ = gNRectangles_; isLocked_ = false; } private void nonOverlap1(int rectangles[], int r, int rEnd, int yTop, int yBottom, int operationType) { int i = gNRectangles_ << 2; if (operationType == OPERATION_UNION || operationType == OPERATION_SUBTRACTION) { while (r != rEnd) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; i++; gRectangles_[i] = yBottom; i++; gRectangles_[i] = rectangles[r + 2]; i++; gRectangles_[i] = rectangles[r + 3]; i++; gNRectangles_++; r += 4; } } } private void nonOverlap2(int rectangles[], int r, int rEnd, int yTop, int yBottom, int operationType) { int i = gNRectangles_ << 2; if (operationType == OPERATION_UNION) { while (r != rEnd) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; i++; gRectangles_[i] = yBottom; i++; gRectangles_[i] = rectangles[r + 2]; i++; gRectangles_[i] = rectangles[r + 3]; i++; gNRectangles_++; r += 4; } } } private void overlap(int rectangles1[], int r1, int r1End, int rectangles2[], int r2, int r2End, int yTop, int yBottom, int operationType) { int i = gNRectangles_ << 2; // // UNION // if (operationType == OPERATION_UNION) { while (r1 != r1End && r2 != r2End) { if (rectangles1[r1 + 2] < rectangles2[r2 + 2]) { if (gNRectangles_ > 0 && gRectangles_[i - 4] == yTop && gRectangles_[i - 3] == yBottom && gRectangles_[i - 1] >= rectangles1[r1 + 2]) { if (gRectangles_[i - 1] < rectangles1[r1 + 3]) gRectangles_[i - 1] = rectangles1[r1 + 3]; } else { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = rectangles1[r1 + 2]; gRectangles_[i + 3] = rectangles1[r1 + 3]; i += 4; gNRectangles_++; } r1 += 4; } else { if (gNRectangles_ > 0 && gRectangles_[i - 4] == yTop && gRectangles_[i - 3] == yBottom && gRectangles_[i - 1] >= rectangles2[r2 + 2]) { if (gRectangles_[i - 1] < rectangles2[r2 + 3]) gRectangles_[i - 1] = rectangles2[r2 + 3]; } else { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = rectangles2[r2 + 2]; gRectangles_[i + 3] = rectangles2[r2 + 3]; i += 4; gNRectangles_++; } r2 += 4; } } if (r1 != r1End) { do { if (gNRectangles_ > 0 && gRectangles_[i - 4] == yTop && gRectangles_[i - 3] == yBottom && gRectangles_[i - 1] >= rectangles1[r1 + 2]) { if (gRectangles_[i - 1] < rectangles1[r1 + 3]) gRectangles_[i - 1] = rectangles1[r1 + 3]; } else { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = rectangles1[r1 + 2]; gRectangles_[i + 3] = rectangles1[r1 + 3]; i += 4; gNRectangles_++; } r1 += 4; } while (r1 != r1End); } else { while (r2 != r2End) { if (gNRectangles_ > 0 && gRectangles_[i - 4] == yTop && gRectangles_[i - 3] == yBottom && gRectangles_[i - 1] >= rectangles2[r2 + 2]) { if (gRectangles_[i - 1] < rectangles2[r2 + 3]) gRectangles_[i - 1] = rectangles2[r2 + 3]; } else { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = rectangles2[r2 + 2]; gRectangles_[i + 3] = rectangles2[r2 + 3]; i += 4; gNRectangles_++; } r2 += 4; } } } // // SUBTRACT // else if (operationType == OPERATION_SUBTRACTION) { int x1 = rectangles1[r1 + 2]; while (r1 != r1End && r2 != r2End) { if (rectangles2[r2 + 3] <= x1) r2 += 4; else if (rectangles2[r2 + 2] <= x1) { x1 = rectangles2[r2 + 3]; if (x1 >= rectangles1[r1 + 3]) { r1 += 4; if (r1 != r1End) x1 = rectangles1[r1 + 2]; } else r2 += 4; } else if (rectangles2[r2 + 2] < rectangles1[r1 + 3]) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i + 0] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = x1; gRectangles_[i + 3] = rectangles2[r2 + 2]; i += 4; gNRectangles_++; x1 = rectangles2[r2 + 3]; if (x1 >= rectangles1[r1 + 3]) { r1 += 4; if (r1 != r1End) x1 = rectangles1[r1 + 2]; else r2 += 4; } } else { if (rectangles1[r1 + 3] > x1) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i + 0] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = x1; gRectangles_[i + 3] = rectangles1[r1 + 3]; i += 4; gNRectangles_++; } r1 += 4; if (r1 != r1End) x1 = rectangles1[r1 + 2]; } } while (r1 != r1End) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i + 0] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = x1; gRectangles_[i + 3] = rectangles1[r1 + 3]; i += 4; gNRectangles_++; r1 += 4; if (r1 != r1End) x1 = rectangles1[r1 + 2]; } } // // INTERSECT // else if (operationType == OPERATION_INTERSECTION) { while (r1 != r1End && r2 != r2End) { int x1 = Math.max(rectangles1[r1 + 2], rectangles2[r2 + 2]); int x2 = Math.min(rectangles1[r1 + 3], rectangles2[r2 + 3]); if (x1 < x2) { checkMemory(null, gNRectangles_ + 1); gRectangles_[i] = yTop; gRectangles_[i + 1] = yBottom; gRectangles_[i + 2] = x1; gRectangles_[i + 3] = x2; i += 4; gNRectangles_++; } if (rectangles1[r1 + 3] < rectangles2[r2 + 3]) r1 += 4; else if (rectangles2[r2 + 3] < rectangles1[r1 + 3]) r2 += 4; else { r1 += 4; r2 += 4; } } } } /** * Corresponds to miCoalesce in Region.c of X11. */ private int coalesceBands(int previousBand, int currentBand) { int r1 = previousBand << 2; int r2 = currentBand << 2; int rEnd = gNRectangles_ << 2; // Number of rectangles in prevoius band int nRectanglesInPreviousBand = currentBand - previousBand; // Number of rectangles in current band int nRectanglesInCurrentBand = 0; int r = r2; int y = gRectangles_[r2]; while (r != rEnd && gRectangles_[r] == y) { nRectanglesInCurrentBand++; r += 4; } // If more than one band was added, we have to find the start // of the last band added so the next coalescing job can start // at the right place. if (r != rEnd) { rEnd -= 4; while (gRectangles_[rEnd - 4] == gRectangles_[rEnd]) rEnd -= 4; currentBand = rEnd >> 2 - gNRectangles_; rEnd = gNRectangles_ << 2; } if (nRectanglesInCurrentBand == nRectanglesInPreviousBand && nRectanglesInCurrentBand != 0) { // The bands may only be coalesced if the bottom of the previous // band matches the top of the current. if (gRectangles_[r1 + 1] == gRectangles_[r2]) { // Chek that the bands have boxes in the same places do { if ((gRectangles_[r1 + 2] != gRectangles_[r2 + 2]) || (gRectangles_[r1 + 3] != gRectangles_[r2 + 3])) return currentBand; // No coalescing r1 += 4; r2 += 4; nRectanglesInPreviousBand--; } while (nRectanglesInPreviousBand != 0); // // OK, the band can be coalesced // // Adjust number of rectangles and set pointers back to start gNRectangles_ -= nRectanglesInCurrentBand; r1 -= nRectanglesInCurrentBand << 2; r2 -= nRectanglesInCurrentBand << 2; // Do the merge do { gRectangles_[r1 + 1] = gRectangles_[r2 + 1]; r1 += 4; r2 += 4; nRectanglesInCurrentBand--; } while (nRectanglesInCurrentBand != 0); // If only one band was added we back up the current pointer if (r2 == rEnd) currentBand = previousBand; else { do { gRectangles_[r1] = gRectangles_[r2]; r1++; r2++; } while (r2 != rEnd); } } } return currentBand; } /** * Update region extent based on rectangle values. */ private void updateExtent() { if (nRectangles_ == 0) extent_.set(0, 0, 0, 0); else { // Y values extent_.y1 = rectangles_[0]; extent_.y2 = rectangles_[(nRectangles_ << 2) - 3]; // X values initialize extent_.x1 = rectangles_[2]; extent_.x2 = rectangles_[3]; // Scan all rectangles for extreme X values for (int i = 4; i < nRectangles_ << 2; i += 4) { if (rectangles_[i + 2] < extent_.x1) extent_.x1 = rectangles_[i + 2]; if (rectangles_[i + 3] > extent_.x2) extent_.x2 = rectangles_[i + 3]; } } } /** * Union this region with the specified region. * Corresponds to XUnionRegion in X11. * * @param region Region to union this with. */ public void union(Region region) { // Trivial case #1. Region is this or empty if (this == region || region.isEmpty()) return; // Trivial case #2. This is empty if (isEmpty()) { set(region); return; } // Trivial case #3. This region covers the specified one if (rectangles_.length == 1 && region.extent_.isInsideOf(extent_)) return; // Trivial case #4. The specified region covers this one if (region.rectangles_.length == 1 && extent_.isInsideOf(region.extent_)) { set(region); return; } // Ceneral case combine(region, OPERATION_UNION); // Update extent extent_.x1 = Math.min(extent_.x1, region.extent_.x1); extent_.y1 = Math.min(extent_.y1, region.extent_.y1); extent_.x2 = Math.max(extent_.x2, region.extent_.x2); extent_.y2 = Math.max(extent_.y2, region.extent_.y2); } /** * Union this region with the specified rectangle. * Corresponds to XUnionRectWithRegion in X11. * * @param rectangle Rectangle to union this with. */ public void union(Rect rectangle) { if (rectangle.isEmpty()) return; union(new Region(rectangle)); } /** * Create a new region as the union between two specified regions. * * @param r1 First region to union. * @param r2 Second region to union. * @return Union of the two specified regions. */ public static Region union(Region r1, Region r2) { Region region = new Region(r1); region.union(r2); return region; } /** * Create a new region as the union between a region and a rectangle. * * @param region Region to union. * @param rectangle Rectangle to intersect with. * @return Union of the region and the rectangle. */ public static Region union(Region region, Rect rectangle) { if (rectangle.isEmpty()) return new Region(region); else return union(region, new Region(rectangle)); } /** * Leave this region as the intersection between this region and the * specified region. * Corresponds to XIntersectRegion in X11. * * @param region Region to intersect this with. */ public void intersect(Region region) { // Trivial case which results in an empty region if (isEmpty() || region.isEmpty() || !extent_.isOverlapping(region.extent_)) { clear(); return; } // General case combine(region, OPERATION_INTERSECTION); // Update extent updateExtent(); } /** * Leave this region as the intersection between this region and the * specified rectangle. * * @param rectangle Rectangle to intersect this with. */ public void intersect(Rect rectangle) { if (rectangle.isEmpty()) clear(); else intersect(new Region(rectangle)); } /** * Create a new region as the intersection between two specified regions. * * @param r1 First region to intersect. * @param r2 Second region to intersect. * @return Intersection between the two specified regions. */ public static Region intersect(Region r1, Region r2) { Region region = new Region(r1); region.intersect(r2); return region; } /** * Create a new region as the intersection between a region and a rectangle. * * @param region Region to intersect. * @param rectangle Rectangle to intersect with. * @return Intersection between the region and the rectangle. */ public static Region intersect(Region region, Rect rectangle) { if (rectangle.isEmpty()) return new Region(); else return intersect(region, new Region(rectangle)); } /** * Subtract the specified region from this region. * Corresponds to XSubtractRegion in X11. * * @param region Region to subtract from this region. */ public void subtract(Region region) { // Trivial check for non-op if (isEmpty() || region.isEmpty() || !extent_.isOverlapping(region.extent_)) return; // General case combine(region, OPERATION_SUBTRACTION); // Update extent updateExtent(); } /** * Subtract the specified rectangle from this region. * * @param rectangle Rectangle to subtract from this region. */ public void subtract(Rect rectangle) { if (rectangle.isEmpty()) return; subtract(new Region(rectangle)); } /** * Create a new region as the subtraction of one region from another. * * @param r1 Region to subtract from. * @param r2 Region to subtract. * @return Subtraction of the two specified regions. */ public static Region subtract(Region r1, Region r2) { Region region = new Region(r1); region.subtract(r2); return region; } /** * Create a new region as the subtraction of a rectangle from * a region. * * @param region Region to subtract from. * @param rectangle Ractangle to subtract. * @return Subtraction of the two specified regions. */ public static Region subtract(Region region, Rect rectangle) { if (rectangle.isEmpty()) return new Region(region); else return subtract(region, new Region(rectangle)); } /** * Leave the exclusive-or between this and the specified region in * this region. Corresponds to the XXorRegion in X11. * * @param region Region to xor this region with. */ public void xor(Region region) { Region r = (Region) region.clone(); r.subtract(this); subtract(region); union(r); } /** * Leave the exclusive-or between this and the specified rectangle in * this region. * * @param rectangle Rectangle to xor this region with. */ public void xor(Rect rectangle) { if (rectangle.isEmpty()) clear(); else xor(new Region(rectangle)); } /** * Do an exlusive-or operation between two regions and return * the result. * * @param r1 First region to xor. * @param r2 Second region to xor. * @return Result of operation. */ public static Region xor(Region r1, Region r2) { Region region = new Region(r1); region.xor(r2); return region; } /** * Do an exlusive-or operation between a regions and a rectangle * and return the result. * * @param region Region to xor. * @param rectangle Rectangle to xor with. * @return Result of operation. */ public static Region xor(Region region, Rect rectangle) { if (rectangle.isEmpty()) return new Region(); else return xor(region, new Region(rectangle)); } // DEBUG private boolean isExtentCorrect() { int yMin = 0; int yMax = 0; int xMin = 0; int xMax = 0; if (nRectangles_ > 0) { yMin = rectangles_[0]; yMax = rectangles_[1]; xMin = rectangles_[2]; xMax = rectangles_[3]; for (int i = 4; i < nRectangles_ << 2; i += 4) { if (rectangles_[i + 0] < yMin) yMin = rectangles_[i + 0]; if (rectangles_[i + 1] > yMax) yMax = rectangles_[i + 1]; if (rectangles_[i + 2] < xMin) xMin = rectangles_[i + 2]; if (rectangles_[i + 3] > xMax) xMax = rectangles_[i + 3]; } } if (extent_.x1 != xMin) { System.out.println("Extent error x1"); return false; } if (extent_.x2 != xMax) { System.out.println("Extent error x2"); return false; } if (extent_.y1 != yMin) { System.out.println("Extent error y1"); return false; } if (extent_.y2 != yMax) { System.out.println("Extent error y2"); return false; } return true; } // DEBUG private boolean isCoalesced() { if (nRectangles_ < 2) return true; int rEnd = nRectangles_ << 2; int thisBand = 0; while (thisBand != rEnd) { // Find start of next band int nextBand = thisBand; while (nextBand != rEnd && rectangles_[nextBand] == rectangles_[thisBand]) nextBand += 4; if (nextBand == rEnd) return true; // Now we have two consecutive bands. See if they touch. if (rectangles_[thisBand + 1] == rectangles_[nextBand + 1]) { // Check the x values int thisY = rectangles_[thisBand]; int nextY = rectangles_[nextBand]; int i = thisBand; int j = nextBand; while (j != rEnd && rectangles_[i] == thisY && rectangles_[j] == nextY) { if (rectangles_[i + 2] != rectangles_[j + 2] || rectangles_[i + 3] != rectangles_[j + 3]) break; i += 4; j += 4; } if (rectangles_[i] != thisY && (rectangles_[j] != nextY || j == rEnd)) System.out.println("Coalesce error at Y=" + thisY); } thisBand = nextBand; } return true; } // DEBUG private boolean isConsistent() { boolean isExtentCorrect = isExtentCorrect(); if (!isExtentCorrect) return false; if (nRectangles_ == 0) return true; for (int i = 0; i < nRectangles_; i += 4) { int y1 = rectangles_[i + 0]; int y2 = rectangles_[i + 1]; int x1 = rectangles_[i + 2]; int x2 = rectangles_[i + 3]; if (y2 <= y1) { System.out.println("Rectangle error y2 > y1"); return false; } if (x2 <= x1) { System.out.println("Rectangle error x2 > x1"); return false; } if (i + 4 < nRectangles_) { int y1next = rectangles_[i + 4]; int y2next = rectangles_[i + 5]; int x1next = rectangles_[i + 6]; int x2next = rectangles_[i + 7]; if (y1next < y1) { System.out.println("Band alignment top error"); return false; } if (y1next == y1) { if (y2next != y2) { System.out.println("Band alignment bottom error"); return false; } if (x1next < x2) { System.out.println("X bands intersect error"); return false; } if (x1next == x2) { System.out.println("X bands touch error"); return false; } } } } if (!isCoalesced()) return false; return true; } // DEBUG private void print() { System.out.println("-------------------------------"); System.out.println(extent_); System.out.println("nRectangles = " + nRectangles_); for (int i = 0; i < nRectangles_; i++) { System.out.print("y1=" + rectangles_[i * 4 + 0] + ", "); System.out.print("y2=" + rectangles_[i * 4 + 1] + ", "); System.out.print("x1=" + rectangles_[i * 4 + 2] + ", "); System.out.println("x2=" + rectangles_[i * 4 + 3]); } } // DEBUG private void printRects() { for (int i = 0; i < nRectangles_ << 2; i++) { if (i % 4 == 0 && i != 0) System.out.print(" "); System.out.print(rectangles_[i]); if ((i + 1) % 4 != 0) System.out.print(','); } System.out.println(); } } /* * This code 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 code 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 program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ //package no.geosoft.cc.geometry; /** * A rectangle defined by its upper left (included) and lower * right (not included) corners. * * <pre> * 1############## * ############### * ############### * 2 * </pre> * * This corresponds to a Rect of width = x2 - x1 and height = y2 - y1. * <p> * Rect and Box represents the same concept, but their different definition * makes them suitable for use in different situations. * * @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a> */ //public class Box implements Cloneable { public int x1; public int x2; public int y1; public int y2; /** * Create an empty box. */ public Box() { set(0, 0, 0, 0); } /** * Create a new box as a copy of the specified box. * * @param box Box to copy. */ public Box(Box box) { set(box.x1, box.y1, box.x2, box.y2); } /** * Create a new box with specified coordinates. The box includes * the (x1,y1) as upper left corner. The lower right corner (x2,y2) * is just outside the box. * * @param x1 X of upper left corner (inclusive). * @param y1 Y of upper left corner (inclusive). * @param x2 X of lower right corner (not inclusive) * @param y2 Y of lower right corner (not inclusive). */ public Box(int x1, int y1, int x2, int y2) { set(x1, y1, x2, y2); } /** * Create a new box based on the specified rectangle. * * @param rectangle Rectangle to copy. */ public Box(Rect rectangle) { x1 = rectangle.x; y1 = rectangle.y; x2 = rectangle.x + rectangle.width; y2 = rectangle.y + rectangle.height; } /** * Copy the specified box. * * @param box Box to copy. */ public void copy(Box box) { set(box.x1, box.y1, box.x2, box.y2); } /** * Clone this box. * * @return Clone of this box. */ public Object clone() { return new Box(x1, y1, x2, y2); } /** * Set the parameters of this box. * * @param x1 X coordinate of upper left corner of box. * @param y1 Y coordinate of upper left corner of box. * @param x2 X coordinate of lower right corner of box. * @param y2 Y coordinate of lower right corner of box. */ public void set(int x1, int y1, int x2, int y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } /** * Check if the specified point is ionside this box. * * @param x X coordinate of point to check. * @param y Y coordinate of point to check. * @return True if the point is inside this box, false otherwise. */ public boolean isInside(int x, int y) { return x >= x1 && x < x2 && y >= y1 && y < y2; } /** * Return true if this box is inside the specified box. * * @param box Box to check if this is inside of. * @return True if this box in inside the specified box, * false otherwise. */ public boolean isInsideOf(Box box) { return x1 >= box.x1 && y1 >= box.y1 && x2 <= box.x2 && y2 <= box.y2; } /** * Return true if this box overlaps the specified box. * * @param box Box to check if this is inside of. * @return True if this box overlaps the specified box, * false otherwise. */ public boolean isOverlapping(Box box) { return x2 > box.x1 && y2 > box.y1 && x1 < box.x2 && y1 < box.y2; } /** * Return true if this box overlaps the specified rectangle. * * @param rectangle Rectnagle to check if this is inside of. * @return True if this box overlaps the specified rectangle, * false otherwise. */ public boolean isOverlapping(Rect rectangle) { return x2 > rectangle.x && x1 < rectangle.x + rectangle.width && y2 > rectangle.y && y1 < rectangle.y + rectangle.height; } /** * Offset this box a specified distance in x and y direction. * * @param dx Offset in x direction. * @param dy Offset in y direction. */ public void offset(int dx, int dy) { x1 += dx; y1 += dy; x2 += dx; y2 += dy; } /** * Return a string representation of this box. * * @return String representation of this box. */ public String toString() { return "Box: " + "y1=" + y1 + " y2=" + y2 + " x1=" + x1 + " x2=" + x2; } } /* * (C) 2004 - Geotechnical Software Services * * This code 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 code 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 program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ //package no.geosoft.cc.geometry; /** * A integer based rectangle. The strange name is to avoid name * clash with java.awt.Rectangle. * <p> * Rect and Box represents the same concept, but their different * definition makes them suitable for use in different situations. * * @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a> */ //public class Rect { public int x; public int y; public int height; public int width; /** * Create a rectangle. * * @param x X coordinate of upper left corner. * @param y Y coordinate of upper left corner. * @param width Width of rectangle. * @param height Height of rectangle. */ public Rect(int x, int y, int width, int height) { set(x, y, width, height); } /** * Create a default rectangle. */ public Rect() { this(0, 0, 0, 0); } /** * Create a rectangle as a copy of the specified rectangle. * * @param rectangle */ public Rect(Rect rectangle) { this(rectangle.x, rectangle.y, rectangle.width, rectangle.height); } /** * Create a rectnagle based on specified box. * * @param box Box to create rectangle from. */ public Rect(Box box) { this(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1); } /** * Copy the specified rectangle. * * @param rectangle Rectangle to copy. */ public void copy(Rect rectangle) { this.x = rectangle.x; this.y = rectangle.y; this.width = rectangle.width; this.height = rectangle.height; } /** * Clone this rectangle * * @return Clone of this rectangle. */ public Object clone() { return new Rect(x, y, width, height); } /** * Check if this rectangle equals the specified object. * * @param object Object to chack. * @return True if the two equals, false otherwise. */ public boolean equals(Object object) { Rect rectangle = (Rect) object; return this.x == rectangle.x && this.y == rectangle.y && this.width == rectangle.width && this.height == rectangle.height; } /** * Return true if this rectangle is empty. * * @return True if this rectangle is empty, false otherwise. */ public boolean isEmpty() { return width <= 0 || height <= 0; } /** * Expand this rectangle the specified amount in each direction. * * @param dx Amount to expand to left and right. * @param dy Amount to expand on top and botton. */ public void expand(int dx, int dy) { x -= dx; y -= dy; width += dx + dx; height += dy + dy; } /** * Set the parameters for this rectangle. * * @param x X coordinate of upper left corner. * @param y Y coordinate of upper left corner. * @param width Width of rectangle. * @param height Height of rectangle. */ public void set(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } /** * Set this rectangle as extent of specified polyline. * * @param xArray X coordinates of polyline. * @param yArray Y coordinates of polyline. */ public void set(int xArray[], int yArray[]) { int minX = Integer.MAX_VALUE; int maxX = Integer.MIN_VALUE; int minY = Integer.MAX_VALUE; int maxY = Integer.MIN_VALUE; for (int i = 0; i < xArray.length; i++) { if (xArray[i] < minX) minX = xArray[i]; if (xArray[i] > maxX) maxX = xArray[i]; if (yArray[i] < minY) minY = yArray[i]; if (yArray[i] > maxY) maxY = yArray[i]; } x = minX; y = minY; width = maxX - minX + 1; height = maxY - minY + 1; } /** * Return X coordinate of center of this rectangle. * * @return X coordinate of center of this rectangle. */ public int getCenterX() { return x + (int) Math.floor(width / 2.0); } /** * Return Y coordinate of center of this rectangle. * * @return Y coordinate of center of this rectangle. */ public int getCenterY() { return y + (int) Math.floor(height / 2.0); } /** * Return a string representation of this rectangle. * * @return String representation of this rectangle. */ public String toString() { return new String("Rectangle: x= " + x + " y=" + y + " width=" + width + " height=" + height); } }