/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.renderer3d.utils; /** * An immutable BoundingRectangle. * * @author Hans H�ggstr�m */ public final class BoundingRectangleImpl implements BoundingRectangle { //====================================================================== // Private Fields private final double myX1; private final double myY1; private final double myX2; private final double myY2; // Keep track of the center coordinate also. // This allows specifying the center coordinate when creating a parent bounding rectangle to be exacly one of the // corner bounding boxes of the child, this ensures there will be no situations where some point lies inside a // bounding rectangle but not in any of it's children (subquadrants) due to numerical innaccuracies resulting // from different ways to calculate the coordinates. // (maybe this is a minor issue that doesn't happen in practice, but at least now it should never happen :-) private final double myCenterX; private final double myCenterY; //====================================================================== // Private Constants private static final int[] OPPOSING_SUBQUADRANT = new int[]{ 3, 2, 1, 0 }; private static final int[] Y_FLIPPED_SUBQUADRANT = new int[]{ 2, 3, 0, 1 }; //====================================================================== // Public Methods //---------------------------------------------------------------------- // Constructors /** * Creates a new axis aligned BoundingRectangleImpl with a given radius (=half side length) around a centerpoint. * * @param centerX x coordinate of the center * @param centerY y coordinate of the center * @param radius distance to expand from the center along each coordinate axis. */ public BoundingRectangleImpl( double centerX, double centerY, double radius ) { this( centerX - radius, centerY - radius, centerX + radius, centerY + radius, centerX, centerY ); } /** * Creates a new axis aligned BoundingRectangleImpl between the two specified points. * The points do not need to be in sorted order, the bounding rectangle will sort them if neccessary. * * @param xa x coordinate of one corner * @param ya y coordinate of one corner * @param xb x coordinate of another corner * @param yb y coordinate of another corner */ public BoundingRectangleImpl( double xa, double ya, double xb, double yb ) { this( xa, ya, xb, yb, ( xa + xb ) * 0.5, ( ya + yb ) * 0.5 ); } //---------------------------------------------------------------------- // BoundingRectangle Implementation public double getX1() { return myX1; } public double getY1() { return myY1; } public double getX2() { return myX2; } public double getY2() { return myY2; } public double getCenterX() { return myCenterX; } public double getCenterY() { return myCenterY; } public boolean isInside( final LocatedDoublePrecisionObject locatedObject ) { return isInside( locatedObject.getX(), locatedObject.getY() ); } public boolean isInside( double x, double y ) { return MathUtils.isInsideRectangle( x, y, myX1, myY1, myX2, myY2 ); } public boolean isInside( final double x, final double y, final double radius ) { return x >= myX1 + radius && x < myX2 - radius && y >= myY1 + radius && y < myY2 - radius; } public boolean isEmpty() { return myX1 >= myX2 || myY1 >= myY2; } public boolean overlaps( BoundingRectangle boundingRectangle ) { return boundingRectangle.getX2() > myX1 && boundingRectangle.getX1() < myX2 && boundingRectangle.getY2() > myY1 && boundingRectangle.getY1() < myY2; } public boolean overlaps( final double x1, final double y1, final double x2, final double y2 ) { return x2 > myX1 && x1 < myX2 && y2 > myY1 && y1 < myY2; } public int getSubquadrantAt( LocatedDoublePrecisionObject position ) { return getSubquadrantAt( position.getX(), position.getY() ); } public int getSubquadrantAt( double x, double y ) { if ( isInside( x, y ) ) { return getSubsectorAt( x, y ); } else { return -1; } } public int getSubsectorAt( LocatedDoublePrecisionObject position ) { return getSubsectorAt( position.getX(), position.getY() ); } public int getSubsectorAt( final double x, final double y ) { final boolean inRightHalf = x >= myCenterX; final boolean inLowerHalf = y >= myCenterY; int subquadrantIndex = 0; if ( inRightHalf ) { subquadrantIndex += 1; } if ( inLowerHalf ) { subquadrantIndex += 2; } return subquadrantIndex; } public BoundingRectangle createSubquadrantBoundingRectangle( int subquadrantIndex ) { ParameterChecker.checkIntegerInRange( subquadrantIndex, "subquadrantIndex", 0, 4 ); double x1 = myX1; double y1 = myY1; double x2 = myX2; double y2 = myY2; if ( inTopRow( subquadrantIndex ) ) { y2 = myCenterY; } else { y1 = myCenterY; } if ( inLeftColumn( subquadrantIndex ) ) { x2 = myCenterX; } else { x1 = myCenterX; } return new BoundingRectangleImpl( x1, y1, x2, y2 ); } public BoundingRectangle createParentBoundingRectangle( final int subsector ) { final double xSize = myX2 - myX1; final double ySize = myY2 - myY1; double x1 = myX1; double y1 = myY1; double x2 = myX2; double y2 = myY2; final double centerX; final double centerY; if ( inTopRow( subsector ) ) { y1 -= ySize; centerY = myY1; } else { y2 += ySize; centerY = myY2; } if ( inLeftColumn( subsector ) ) { x1 -= xSize; centerX = myX1; } else { x2 += xSize; centerX = myX2; } return new BoundingRectangleImpl( x1, y1, x2, y2, centerX, centerY ); } public int getOppositeSubquadrant( int subquadrant ) { ParameterChecker.checkIntegerInRange( subquadrant, "subquadrant", 0, 4 ); return OPPOSING_SUBQUADRANT[ subquadrant ]; } public int flipSubquadrantAcrossY( final int subquadrant ) { ParameterChecker.checkIntegerInRange( subquadrant, "subquadrant", 0, 4 ); return Y_FLIPPED_SUBQUADRANT[ subquadrant ]; } public double getSizeX() { return myX2 - myX1; } public double getSizeY() { return myY2 - myY1; } public double getSizeAveraged() { return 0.5 * ( getSizeX() + getSizeY() ); } public BoundingRectangle transform( final double translationX, final double translationY, final double scaleX, final double scaleY ) { return new BoundingRectangleImpl( ( myX1 ) * scaleX + translationX, ( myY1 ) * scaleY + translationY, ( myX2 ) * scaleX + translationX, ( myY2 ) * scaleY + translationY, ( myCenterX ) * scaleX + translationX, ( myCenterY ) * scaleY + translationY ); } //---------------------------------------------------------------------- // Caononical Methods public boolean equals( final Object o ) { if ( this == o ) { return true; } if ( o == null || getClass() != o.getClass() ) { return false; } final BoundingRectangleImpl that = (BoundingRectangleImpl) o; if ( Double.compare( that.myCenterX, myCenterX ) != 0 ) { return false; } if ( Double.compare( that.myCenterY, myCenterY ) != 0 ) { return false; } if ( Double.compare( that.myX1, myX1 ) != 0 ) { return false; } if ( Double.compare( that.myX2, myX2 ) != 0 ) { return false; } if ( Double.compare( that.myY1, myY1 ) != 0 ) { return false; } if ( Double.compare( that.myY2, myY2 ) != 0 ) { return false; } return true; } public int hashCode() { int result; long temp; temp = myX1 != +0.0d ? Double.doubleToLongBits( myX1 ) : 0L; result = (int) ( temp ^ ( temp >>> 32 ) ); temp = myY1 != +0.0d ? Double.doubleToLongBits( myY1 ) : 0L; result = 31 * result + (int) ( temp ^ ( temp >>> 32 ) ); temp = myX2 != +0.0d ? Double.doubleToLongBits( myX2 ) : 0L; result = 31 * result + (int) ( temp ^ ( temp >>> 32 ) ); temp = myY2 != +0.0d ? Double.doubleToLongBits( myY2 ) : 0L; result = 31 * result + (int) ( temp ^ ( temp >>> 32 ) ); temp = myCenterX != +0.0d ? Double.doubleToLongBits( myCenterX ) : 0L; result = 31 * result + (int) ( temp ^ ( temp >>> 32 ) ); temp = myCenterY != +0.0d ? Double.doubleToLongBits( myCenterY ) : 0L; result = 31 * result + (int) ( temp ^ ( temp >>> 32 ) ); return result; } public String toString() { return "BoundingRectangleImpl{" + "x1=" + myX1 + ", y1=" + myY1 + ", x2=" + myX2 + ", y2=" + myY2 + ", centerX=" + myCenterX + ", centerY=" + myCenterY + '}'; } //====================================================================== // Private Methods /** * Private constructor that also takes the center coordinates as input. */ private BoundingRectangleImpl( double xa, double ya, double xb, double yb, double centerX, double centerY ) { ParameterChecker.checkNormalNumber( xa, "xa" ); ParameterChecker.checkNormalNumber( ya, "ya" ); ParameterChecker.checkNormalNumber( xb, "xb" ); ParameterChecker.checkNormalNumber( yb, "yb" ); ParameterChecker.checkNormalNumber( centerX, "centerX" ); ParameterChecker.checkNormalNumber( centerY, "centerY" ); // Swap if necessary to make xa <= xb and ya <= yb if ( xa > xb ) { final double xt = xa; xa = xb; xb = xt; } if ( ya > yb ) { final double yt = ya; ya = yb; yb = yt; } myX1 = xa; myY1 = ya; myX2 = xb; myY2 = yb; myCenterX = centerX; myCenterY = centerY; } private boolean inLeftColumn( final int subquadrantIndex ) { return subquadrantIndex == 0 || subquadrantIndex == 2; } private boolean inTopRow( final int subquadrantIndex ) { return subquadrantIndex == 0 || subquadrantIndex == 1; } }