/*
* (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);
}
}
|