All Downloads are FREE. Search and download functionalities are using the official Maven repository.

impl.org.controlsfx.tools.rectangle.Rectangles2D Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2014, ControlsFX
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of ControlsFX, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package impl.org.controlsfx.tools.rectangle;

import impl.org.controlsfx.tools.MathTools;

import java.util.Objects;

import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;

/**
 * Usability methods for rectangles.
 */
public class Rectangles2D {

    /*
     * CHECKS
     */

    /**
     * Indicates whether the specified rectangle contains the specified edge.
     * 
     * @param rectangle
     *            the rectangle to check
     * @param edge
     *            the edge to check
     * @return {@code true} if both end points of the edge are {@link Rectangle2D#contains(Point2D) contained} in the
     *         rectangle
     */
    public static boolean contains(Rectangle2D rectangle, Edge2D edge) {
        Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$
        Objects.requireNonNull(edge, "The argument 'edge' must not be null."); //$NON-NLS-1$

        boolean edgeInBounds = rectangle.contains(edge.getUpperLeft()) && rectangle.contains(edge.getLowerRight());
        return edgeInBounds;
    }

    /*
     * POINT
     */

    /**
     * Moves the specified point into the specified rectangle. If the point is already with the rectangle, it is
     * returned. Otherwise the point in the rectangle which is closest to the specified one is returned.
     * 
     * @param rectangle
     *            the {@link Rectangle2D} into which the point should be moved
     * @param point
     *            the {@link Point2D} which is checked
     * @return either the specified {@code point} or the {@link Point2D} which is closest to it while still being
     *         contained on the {@code rectangle}
     */
    public static Point2D inRectangle(Rectangle2D rectangle, Point2D point) {
        Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$
        Objects.requireNonNull(point, "The argument 'point' must not be null."); //$NON-NLS-1$

        if (rectangle.contains(point)) {
            return point;
        }

        // force the x and y coordinate into the rectangle
        double newX = MathTools.inInterval(rectangle.getMinX(), point.getX(), rectangle.getMaxX());
        double newY = MathTools.inInterval(rectangle.getMinY(), point.getY(), rectangle.getMaxY());
        return new Point2D(newX, newY);
    }

    /**
     * Returns the center of the specified rectangle as a point.
     * 
     * @param rectangle
     *            the {@link Rectangle2D} whose center point will be returned
     * @return the {@link Point2D} whose x/y coordinates lie at {@code (min + max) / 2}.
     */
    public static Point2D getCenterPoint(Rectangle2D rectangle) {
        Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$

        double centerX = (rectangle.getMinX() + rectangle.getMaxX()) / 2;
        double centerY = (rectangle.getMinY() + rectangle.getMaxY()) / 2;
        return new Point2D(centerX, centerY);
    }

    /*
     * OTHER RECTANGLE
     */

    /**
     * Returns the rectangle which represents the intersection of the two specified rectangles.
     * 
     * @param a
     *            a {@link Rectangle2D}
     * @param b
     *            another {@link Rectangle2D}
     * @return a {@link Rectangle2D} which is the intersection of {@code a} and {@code b}; possible
     *         {@link Rectangle2D#EMPTY}.
     */
    public static Rectangle2D intersection(Rectangle2D a, Rectangle2D b) {
        Objects.requireNonNull(a, "The argument 'a' must not be null."); //$NON-NLS-1$
        Objects.requireNonNull(b, "The argument 'b' must not be null."); //$NON-NLS-1$

        if (a.intersects(b)) {
            double intersectionMinX = Math.max(a.getMinX(), b.getMinX());
            double intersectionMaxX = Math.min(a.getMaxX(), b.getMaxX());
            double intersectionWidth = intersectionMaxX - intersectionMinX;
            double intersectionMinY = Math.max(a.getMinY(), b.getMinY());
            double intersectionMaxY = Math.min(a.getMaxY(), b.getMaxY());
            double intersectionHeight = intersectionMaxY - intersectionMinY;
            return new Rectangle2D(intersectionMinX, intersectionMinY, intersectionWidth, intersectionHeight);
        } else {
            return Rectangle2D.EMPTY;
        }
    }

    /*
     * TWO CORNERS
     */

    /**
     * Creates a new rectangle with the two specified corners. The two corners will be interpreted as being diagonal of
     * each other.
     * 
     * @param oneCorner
     *            one corner
     * @param diagonalCorner
     *            another corner, diagonal from the first
     * @return the {@link Rectangle2D} which is defined by the two corners
     */
    public static Rectangle2D forDiagonalCorners(Point2D oneCorner, Point2D diagonalCorner) {
        Objects.requireNonNull(oneCorner, "The specified corner must not be null."); //$NON-NLS-1$
        Objects.requireNonNull(diagonalCorner, "The specified diagonal corner must not be null."); //$NON-NLS-1$

        double minX = Math.min(oneCorner.getX(), diagonalCorner.getX());
        double minY = Math.min(oneCorner.getY(), diagonalCorner.getY());
        double width = Math.abs(oneCorner.getX() - diagonalCorner.getX());
        double height = Math.abs(oneCorner.getY() - diagonalCorner.getY());

        return new Rectangle2D(minX, minY, width, height);
    }

    /*
     * CORNER AND SIZE
     */

    /**
     * Creates a new rectangle with the specified {@code upperLeft} corner and the specified {@code width} and
     * {@code height}.
     * 
     * @param upperLeft
     *            one corner
     * @param width
     *            the new rectangle's width
     * @param height
     *            the new rectangle's height
     * @return the {@link Rectangle2D} which is defined by the specified upper left corner and width and height
     */
    public static Rectangle2D forUpperLeftCornerAndSize(Point2D upperLeft, double width, double height) {
        return new Rectangle2D(upperLeft.getX(), upperLeft.getY(), width, height);
    }

    /*
     * CORNER AND RATIO
     */

    /**
     * Creates a new rectangle with the two specified corners. The two corners will be interpreted as being diagonal of
     * each other. The returned rectangle will have the specified {@code fixedCorner} as its corner. The other one will
     * either be on the same x- or y-parallel as the {@code diagonalCorner} but will be such that the rectangle has the
     * specified {@code ratio}.
     * 
     * @param fixedCorner
     *            one corner
     * @param diagonalCorner
     *            another corner, diagonal from the first
     * @param ratio
     *            the ratio the returned rectangle must have; must be non-negative
     * @return the {@link Rectangle2D} which is defined by the {@code fixedCorner}, the x- or y-parallel of the
     *         {@code diagonalCorner} and the {@code ratio}
     */
    public static Rectangle2D forDiagonalCornersAndRatio(Point2D fixedCorner, Point2D diagonalCorner, double ratio) {
        Objects.requireNonNull(fixedCorner, "The specified fixed corner must not be null."); //$NON-NLS-1$
        Objects.requireNonNull(diagonalCorner, "The specified diagonal corner must not be null."); //$NON-NLS-1$
        if (ratio < 0) {
            throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$
        }

        // the coordinate differences - note that they can be negative
        double xDifference = diagonalCorner.getX() - fixedCorner.getX();
        double yDifference = diagonalCorner.getY() - fixedCorner.getY();

        // the following calls will only change one of the two differences:
        // the one whose value is too large compared to what it should be based on the other difference and the ratio;
        // its value will instead be the other difference time or divided by the ratio
        double xDifferenceByRatio = correctCoordinateDifferenceByRatio(xDifference, yDifference, ratio);
        double yDifferenceByRatio = correctCoordinateDifferenceByRatio(yDifference, xDifference, 1 / ratio);

        // these are the coordinates of the upper left corner of the future rectangle
        double minX = getMinCoordinate(fixedCorner.getX(), xDifferenceByRatio);
        double minY = getMinCoordinate(fixedCorner.getY(), yDifferenceByRatio);

        double width = Math.abs(xDifferenceByRatio);
        double height = Math.abs(yDifferenceByRatio);

        return new Rectangle2D(minX, minY, width, height);
    }

    /**
     * Returns the difference with the following properties:
* - it has the same sign as the specified difference
* - its absolute value is the minimum of the absolute values of ...
* ... the specified difference and
* ... the product of the specified ratio and the other specified difference
* * @param difference * the difference to check * @param otherDifference * the other difference * @param ratioAsMultiplier * the ratio as a multiplier for the other difference * @return the corrected difference */ private static double correctCoordinateDifferenceByRatio(double difference, double otherDifference, double ratioAsMultiplier) { double differenceByRatio = otherDifference * ratioAsMultiplier; double correctedDistance = Math.min(Math.abs(difference), Math.abs(differenceByRatio)); return correctedDistance * Math.signum(difference); } /** * Returns the minimum coordinate such that a rectangle starting from that coordinate will contain the fixed * coordinate as a corner. * * @param fixedCoordinate * the coordinate which must be a corner * @param difference * the difference in the computed coordinate * @return fixedCoordinate + difference; if difference < 0
* fixedCoordinate; else */ private static double getMinCoordinate(double fixedCoordinate, double difference) { if (difference < 0) { return fixedCoordinate + difference; } return fixedCoordinate; } /* * CENTER AND SIZE */ /** * Creates a new rectangle with the specified center and the specified width and height. * * @param centerPoint * the center point o the new rectangle * @param width * the width of the new rectangle * @param height * the height of the new rectangle * @return a rectangle with the specified center and size */ public static Rectangle2D forCenterAndSize(Point2D centerPoint, double width, double height) { Objects.requireNonNull(centerPoint, "The specified center point must not be null."); //$NON-NLS-1$ double absoluteWidth = Math.abs(width); double absoluteHeight = Math.abs(height); double minX = centerPoint.getX() - absoluteWidth / 2; double minY = centerPoint.getY() - absoluteHeight / 2; return new Rectangle2D(minX, minY, width, height); } /* * ORIGINAL, AREA AND RATIO */ /** * Creates a new rectangle with the same center point and area as the specified {@code original} rectangle with the * specified {@code ratio}. * * @param original * the original rectangle * @param ratio * the new ratio * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified * {@code ratio}; it has the same area as the {@code original} * @throws NullPointerException * if the {@code original} rectangle is null */ public static Rectangle2D fixRatio(Rectangle2D original, double ratio) { Objects.requireNonNull(original, "The specified original rectangle must not be null."); //$NON-NLS-1$ if (ratio < 0) { throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } return createWithFixedRatioWithinBounds(original, ratio, null); } /** * Creates a new rectangle with the same center point and area (if possible) as the specified {@code original} * rectangle with the specified {@code ratio} and respecting the specified {@code bounds}. * * @param original * the original rectangle * @param ratio * the new ratio * @param bounds * the bounds within which the new rectangle will be located * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified * {@code ratio}; it has the same area as the {@code original} unless this would violate the bounds; in this * case it is as large as possible while still staying within the bounds * @throws NullPointerException * if the {@code original} or {@code bounds} rectangle is null * @throws IllegalArgumentException * if the {@code original} rectangle's center point is out of the bounds */ public static Rectangle2D fixRatioWithinBounds(Rectangle2D original, double ratio, Rectangle2D bounds) { Objects.requireNonNull(original, "The specified original rectangle must not be null."); //$NON-NLS-1$ Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ if (ratio < 0) { throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } return createWithFixedRatioWithinBounds(original, ratio, bounds); } /** * Creates a new rectangle with the same center point and area as the specified {@code original} rectangle with the * specified {@code ratio} and respecting the specified {@code bounds} (if not-{@code null}). * * @param original * the original rectangle * @param ratio * the new ratio * @param bounds * the bounds within which the new rectangle will be located; might be {@code null} * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified * {@code ratio}; it has the same area as the {@code original} unless this would violate the bounds; in this * case it is as large as possible while still staying within the bounds * @throws IllegalArgumentException * if the {@code original} rectangle's center point is out of the {@code bounds} */ private static Rectangle2D createWithFixedRatioWithinBounds(Rectangle2D original, double ratio, Rectangle2D bounds) { Point2D centerPoint = getCenterPoint(original); boolean centerPointInBounds = bounds == null || bounds.contains(centerPoint); if (!centerPointInBounds) { throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ } double area = original.getWidth() * original.getHeight(); return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, bounds); } /* * CENTER, AREA AND RATIO */ /** * Creates a new rectangle with the specified {@code centerPoint}, {@code area} and {@code ratio}. * * @param centerPoint * the new rectangle's center point * @param area * the new rectangle's area * @param ratio * the new ratio * @return a new {@link Rectangle2D} with the specified {@code centerPoint}, {@code area} and {@code ratio} * @throws IllegalArgumentException * if the {@code centerPoint} is out of the {@code bounds} */ public static Rectangle2D forCenterAndAreaAndRatio(Point2D centerPoint, double area, double ratio) { Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ if (area < 0) { throw new IllegalArgumentException("The specified area " + area + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } if (ratio < 0) { throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, null); } /** * Creates a new rectangle with the specified {@code centerPoint}, {@code area} (if possible) and {@code ratio}, * respecting the specified {@code bounds}. * * @param centerPoint * the new rectangle's center point * @param area * the new rectangle's area (if possible without violating the bounds) * @param ratio * the new ratio * @param bounds * the bounds within which the new rectangle will be located * @return a new {@link Rectangle2D} with the specified {@code centerPoint} and {@code ratio}; it has the specified * {@code area} unless this would violate the {@code bounds}; in this case it is as large as possible while * still staying within the bounds * @throws IllegalArgumentException * if the {@code centerPoint} is out of the {@code bounds} */ public static Rectangle2D forCenterAndAreaAndRatioWithinBounds( Point2D centerPoint, double area, double ratio, Rectangle2D bounds) { Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ boolean centerPointInBounds = bounds.contains(centerPoint); if (!centerPointInBounds) { throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ } if (area < 0) { throw new IllegalArgumentException("The specified area " + area + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } if (ratio < 0) { throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, bounds); } /** * Creates a new rectangle with the specified {@code centerPoint}, {@code area} (if possible) and {@code ratio}, * respecting the specified {@code bounds} (if not-null). * * @param centerPoint * the new rectangle's center point * @param area * the new rectangle's area (if possible without violating the bounds) * @param ratio * the new ratio * @param bounds * the bounds within which the new rectangle will be located * @return a new {@link Rectangle2D} with the specified {@code centerPoint} and {@code ratio}; it has the specified * {@code area} unless this would violate the {@code bounds}; in this case it is as large as possible while * still staying within the bounds * @throws IllegalArgumentException * if the {@code centerPoint} is out of the {@code bounds} */ private static Rectangle2D createForCenterAreaAndRatioWithinBounds( Point2D centerPoint, double area, double ratio, Rectangle2D bounds) { double newWidth = Math.sqrt(area * ratio); double newHeight = area / newWidth; boolean boundsSpecified = bounds != null; if (boundsSpecified) { double reductionFactor = lengthReductionToStayWithinBounds(centerPoint, newWidth, newHeight, bounds); newWidth *= reductionFactor; newHeight *= reductionFactor; } return Rectangles2D.forCenterAndSize(centerPoint, newWidth, newHeight); } /** * Computes the factor by which the specified width and height must be multiplied to keep a rectangle with their * ratio and the specified center point within the specified bounds. * * @param centerPoint * the center point of the new rectangle * @param width * the original width which might be too large * @param height * the original height which might be too large * @param bounds * the bounds within which the new rectangle will be located * @return the factor with which the width and height must be multiplied to stay within the bounds; always in the * closed interval [0; 1] * @throws IllegalArgumentException * if the {@code centerPoint} is out of the {@code bounds}; if {@code width} or {@code height} are not * larger than zero */ private static double lengthReductionToStayWithinBounds( Point2D centerPoint, double width, double height, Rectangle2D bounds) { Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ boolean centerPointInBounds = bounds.contains(centerPoint); if (!centerPointInBounds) { throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ } if (width < 0) { throw new IllegalArgumentException("The specified width " + width + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } if (height < 0) { throw new IllegalArgumentException("The specified height " + height + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } /* * Compute the center point's distance to all edges. The width and height must be reduced (by the returned * factor) such that their halves (!) are not greater than those distances. This can be done by finding the * minimum ratio between the distance and the halved width/height. */ double distanceToEast = Math.abs(centerPoint.getX() - bounds.getMinX()); double distanceToWest = Math.abs(centerPoint.getX() - bounds.getMaxX()); double distanceToNorth = Math.abs(centerPoint.getY() - bounds.getMinY()); double distanceToSouth = Math.abs(centerPoint.getY() - bounds.getMaxY()); // the returned factor must not be greater than one; otherwise the size would increase return MathTools.min(1, distanceToEast / width * 2, distanceToWest / width * 2, distanceToNorth / height * 2, distanceToSouth / height * 2); } /* * EDGES */ /** * Returns a rectangle that has the specified edge and has its opposing edge on the parallel axis defined by the * specified point's X or Y coordinate (depending on the edge's orientation). * * @param edge * the edge which will be contained in the returned rectangle * @param point * the point whose X or Y coordinate defines the other edge * @return a rectangle */ public static Rectangle2D forEdgeAndOpposingPoint(Edge2D edge, Point2D point) { double otherDimension = edge.getOrthogonalDifference(point); return createForEdgeAndOtherDimension(edge, otherDimension); } /** * Returns a rectangle that is principally defined by the specified edge and point. It should have the specified * edge as one of its own and its parallel edge should contain the point. While this would already well-define the * rectangle (compare {@link #forEdgeAndOpposingPoint(Edge2D, Point2D) forEdgeAndOpposingPoint}) the additionally * specified ratio and bounds have precedence over these arguments:
* The returned rectangle will have the ratio and will be within the bounds. If the bounds make it possible, the * specified point will lie on the edge parallel to the specified one. In order to maintain the ratio, this will * make it necessary to not use the specified edge but instead one with a different length. The new edge will have * the same center point as the specified one.
* This results on the following behavior: As the point is moved closer to or further away from the edge, the * resulting rectangle shrinks and grows while being anchored to the specified edge's center point and keeping the * ratio. This is limited by the bounds. * * @param edge * the edge which defines the center point and orientation of one of the rectangle's edges; must be * within the specified {@code bounds} * @param point * the point to which the rectangle spans if ratio and bounds allow it * @param ratio * the ratio the new rectangle must have * @param bounds * the bounds within which the new rectangle must lie * @return a rectangle */ public static Rectangle2D forEdgeAndOpposingPointAndRatioWithinBounds( Edge2D edge, Point2D point, double ratio, Rectangle2D bounds) { Objects.requireNonNull(edge, "The specified edge must not be null."); //$NON-NLS-1$ Objects.requireNonNull(point, "The specified point must not be null."); //$NON-NLS-1$ Objects.requireNonNull(bounds, "The specified bounds must not be null."); //$NON-NLS-1$ boolean edgeInBounds = contains(bounds, edge); if (!edgeInBounds) { throw new IllegalArgumentException( "The specified edge " + edge + " is not entirely contained on the specified bounds."); //$NON-NLS-1$ //$NON-NLS-2$ } if (ratio < 0) { throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ } /* * 1. move the point into the bounds * 2. create an edge whose length matches the distance to the point and the ratio * 3. correct that edge so that it lies within the bounds * 4. create a new rectangle from that edge and the ratio */ Point2D boundedPoint = movePointIntoBounds(point, bounds); Edge2D unboundedEdge = resizeEdgeForDistanceAndRatio(edge, boundedPoint, ratio); Edge2D boundedEdge = resizeEdgeForBounds(unboundedEdge, bounds); // when computing the other dimension, note that the sign of the original difference between edge and point is // important; otherwise the "direction" of the resize is wrong double otherDimension = Math.signum(boundedEdge.getOrthogonalDifference(boundedPoint)); if (boundedEdge.isHorizontal()) { // edge horizontal -> width fixed -> use length to compute height otherDimension *= boundedEdge.getLength() / ratio; } else { // edge vertical -> height fixed -> use length to compute width otherDimension *= boundedEdge.getLength() * ratio; } return createForEdgeAndOtherDimension(boundedEdge, otherDimension); } /** * Returns either the specified point if if the specified bounds {@link Rectangle2D#contains(Point2D) contain} it or * a point whose X and/or Y coordinates are moved into the bounds. * * @param point * the point to move into the {@code bounds} * @param bounds * the bounds into which the {@code point} will be moved * @return either {@code point} or a new {@link Point2D} whose coordinates were changed so that it lies within the * {@code bounds} */ private static Point2D movePointIntoBounds(Point2D point, Rectangle2D bounds) { if (bounds.contains(point)) { return point; } else { double boundedPointX = MathTools.inInterval(bounds.getMinX(), point.getX(), bounds.getMaxX()); double boundedPointY = MathTools.inInterval(bounds.getMinY(), point.getY(), bounds.getMaxY()); return new Point2D(boundedPointX, boundedPointY); } } /** * Returns an edge with the same center point and orientation as the specified edge. Its length has the specified * ratio to the distance of the edge and the specified point. * * @param edge * the edge whose center point and orientation defines the returned edge's center point and orientation * @param point * the point to which the distance is measured * @param ratio * the ratio between the distance to the {@code point} and the returned edge's length * @return an {@link Edge2D} */ private static Edge2D resizeEdgeForDistanceAndRatio(Edge2D edge, Point2D point, double ratio) { double distance = Math.abs(edge.getOrthogonalDifference(point)); if (edge.isHorizontal()) { // a horizontal edge's length lies in the X axis; the distance lies in the Y axis: x = y * ratio double xLength = distance * ratio; return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), xLength); } else { // a vertical edge's length lies in the Y axis; the distance lies in the X axis: y = x / ratio double yLength = distance / ratio; return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), yLength); } } /** * Returns an edge with the same center point and orientation as the specified edge. If necessary, its length is * reduced to fit within the bounds. * * @param edge * the edge whose center point and orientation defines the returned edge's center point and orientation; * the center point must be within the bounds or an {@link IllegalArgumentException} will be thrown * @param bounds * the bounds within which the returned edge must be contained * @return either the specified {@code edge} if it is with in the {@code bounds} or one with a corrected length * @throws IllegalArgumentException * if the {@code edge}'s center point is out of {@code bounds} */ private static Edge2D resizeEdgeForBounds(Edge2D edge, Rectangle2D bounds) { // return the same edge if it is in the bounds boolean edgeInBounds = contains(bounds, edge); if (edgeInBounds) { return edge; } // make sure the bounds contain the edge's center point boolean centerPointInBounds = bounds.contains(edge.getCenterPoint()); if (!centerPointInBounds) { throw new IllegalArgumentException( "The specified edge's center point (" + edge + ") is out of the specified bounds (" + bounds + ")."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } if (edge.isHorizontal()) { // compute the length bounds for the left and right part of the edge double leftPartLengthBound = Math.abs(bounds.getMinX() - edge.getCenterPoint().getX()); double rightPartLengthBound = Math.abs(bounds.getMaxX() - edge.getCenterPoint().getX()); // compute the length of the left and right parts of the edge double leftPartLength = MathTools.inInterval(0, edge.getLength() / 2, leftPartLengthBound); double rightPartLength = MathTools.inInterval(0, edge.getLength() / 2, rightPartLengthBound); // compute the total length as double of the smaller length double horizontalLength = Math.min(leftPartLength, rightPartLength) * 2; return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), horizontalLength); } else { // compute the length bounds for the lower and upper part of the edge double lowerPartLengthBound = Math.abs(bounds.getMinY() - edge.getCenterPoint().getY()); double upperPartLengthBound = Math.abs(bounds.getMaxY() - edge.getCenterPoint().getY()); // compute the length of the lower and upper part of the edge double lowerPartLength = MathTools.inInterval(0, edge.getLength() / 2, lowerPartLengthBound); double upperPartLength = MathTools.inInterval(0, edge.getLength() / 2, upperPartLengthBound); // compute the total length as double of the smaller length double verticalLength = Math.min(lowerPartLength, upperPartLength) * 2; return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), verticalLength); } } /** * Returns a rectangle that has the specified edge and a height or width (depending on the edge's orientation) as * specified by {@code otherDimension}. * * @param edge * the edge which will be contained in the returned rectangle * @param otherDimension * if the edge's orientation is {@link Orientation#HORIZONTAL horizontal}, this is interpreted as the * height; if the edge's orientation is {@link Orientation#VERTICAL vertical}, this is interpreted as the * width * @return a rectangle */ private static Rectangle2D createForEdgeAndOtherDimension(Edge2D edge, double otherDimension) { if (edge.isHorizontal()) { return createForHorizontalEdgeAndHeight(edge, otherDimension); } else { return createForVerticalEdgeAndWidth(edge, otherDimension); } } /** * Returns a rectangle that has the specified horizontal edge and height. Depending on whether the width is positive * or negative, the specified edge will be the upper or lower edge of the returned rectangle. * * @param horizontalEdge * the horizontal edge which will be contained in the returned rectangle * @param height * the returned rectangle's height * @return a rectangle */ private static Rectangle2D createForHorizontalEdgeAndHeight(Edge2D horizontalEdge, double height) { Point2D leftEdgeEndPoint = horizontalEdge.getUpperLeft(); double upperLeftX = leftEdgeEndPoint.getX(); // if the height is negative, reduce the Y coordinate by that amount double upperLeftY = leftEdgeEndPoint.getY() + Math.min(0, height); double absoluteWidth = Math.abs(horizontalEdge.getLength()); double absoluteHeight = Math.abs(height); return new Rectangle2D(upperLeftX, upperLeftY, absoluteWidth, absoluteHeight); } /** * Returns a rectangle that has the specified horizontal edge and width. Depending on whether the width is positive * or negative, the specified edge will be the left or right edge of the returned rectangle. * * @param verticalEdge * the vertical edge which will be contained in the returned rectangle * @param width * the returned rectangle's height * @return a rectangle */ private static Rectangle2D createForVerticalEdgeAndWidth(Edge2D verticalEdge, double width) { Point2D upperEdgeEndPoint = verticalEdge.getUpperLeft(); // if the width is negative, reduce the X coordinate by that amount double upperLeftX = upperEdgeEndPoint.getX() + Math.min(0, width); double upperLeftY = upperEdgeEndPoint.getY(); double absoluteWidth = Math.abs(width); double absoluteHeight = Math.abs(verticalEdge.getLength()); return new Rectangle2D(upperLeftX, upperLeftY, absoluteWidth, absoluteHeight); } /* * MISC */ /** * Returns a rectangle with the same coordinates as the specified bounds. * * @param bounds * the {@link Bounds} for which the rectangle will be created * @return a {@link Rectangle2D} with the same minX-, minY-, maxX- and maxY-coordiantes as the specified bounds */ public static Rectangle2D fromBounds(Bounds bounds) { return new Rectangle2D(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy