Region.java Source code

Java tutorial

Introduction

Here is the source code for Region.java

Source

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

}