/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.api.gui.util; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; public final class GeomUtil { private GeomUtil() { } public static boolean isLineValid(Point2D ptA, Point2D ptB) { return ptA != null && ptB != null && !ptA.equals(ptB); } /** * @return angle between BA & BC line segment in Degree <br> * 0 is returned if any argument is invalid */ public static double getAngleRad(Point2D ptA, Point2D ptB, Point2D ptC) { if (ptA != null && ptB != null && ptC != null) { return getAngleRad(ptB, ptC) - getAngleRad(ptB, ptA); } return 0; } /** * @return angle between BA & BC line segment in Radiant<br> * 0 is returned if any argument is invalid */ public static double getAngleDeg(Point2D ptA, Point2D ptB, Point2D ptC) { if (ptA != null && ptB != null && ptC != null) { return Math.toDegrees(getAngleRad(ptA, ptB, ptC)); } return 0; } /** * Compute angle into image system basis where positive angle are defined in a ClockWise orientation<br> * Note : angle should be computed with "Math.atan2(ptB.getY() - ptA.getY(), ptB.getX() - ptA.getX())" in an * ortho-normal basis system where positive angle is defined in a CounterClockWise orientation. * * @return angle of AB line segment in radiant<br> * 0 is returned if any argument is invalid */ public static double getAngleRad(Point2D ptA, Point2D ptB) { return (ptA != null && ptB != null) ? Math.atan2(ptA.getY() - ptB.getY(), ptB.getX() - ptA.getX()) : null; } /** * @return angle of AB line segment in radiant<br> * 0 is returned if any argument is invalid */ public static double getAngleDeg(Point2D ptA, Point2D ptB) { return (ptA != null && ptB != null) ? Math.toDegrees(getAngleRad(ptA, ptB)) : null; } /** * @param angle * in Radiant * @return angle in the range of [ -pi ; pi ] */ public static double getSmallestRotationAngleRad(double angle) { double a = angle % (2 * Math.PI); if (Math.abs(a) > Math.PI) { a -= Math.signum(a) * (2.0 * Math.PI); } return a; } /** * @param angle * in Degree * @return angle in the range of [ -180 ; 180 ] */ public static double getSmallestRotationAngleDeg(double angle) { double a = angle % 360.0; if (Math.abs(a) > 180.0) { a -= Math.signum(a) * 360.0; } return a; } /** * @param angle * in Radiant * @return angle in the range of [ -pi ; pi ] */ public static double getSmallestAngleRad(double angle) { double a = angle % Math.PI; if (Math.abs(a) > (Math.PI / 2.0)) { a -= Math.signum(a) * Math.PI; } return a; } /** * @param angle * in Degree * @return angle in the range of [ -90 ; 90 ] */ public static double getSmallestAngleDeg(double angle) { double a = angle % 180.0; if (Math.abs(a) > 90.0) { a -= Math.signum(a) * 180.0; } return a; } /** * @return midPoint or null if any argument is invalid */ public static Point2D.Double getMidPoint(Point2D ptA, Point2D ptB) { if (ptA != null && ptB != null) { return new Point2D.Double((ptA.getX() + ptB.getX()) / 2.0, (ptA.getY() + ptB.getY()) / 2.0); } return null; } /** * @param ptA * the first point of line segment * @param ptB * the last point of line segment * @param newLength * represents length from ptA to the return point ptC along AB line segment.<br> * If >AB , ptC point will be located on extension of AB<br> * If <0 , ptC point will be located on extension of BA <br> * If >0 && < AB , ptC point will be interior of AB<br> * @return New point ptC coordinates or null if any argument is invalid */ public static Point2D.Double getColinearPointWithLength(Point2D ptA, Point2D ptB, double newLength) { if (ptA != null && ptB != null) { return getColinearPointWithRatio(ptA, ptB, newLength / ptA.distance(ptB)); } return null; } /** * @param ptA * first point of line segment * @param ptB * last point of line segment * @param k * represents ratio between AB and AC, ptC being the returned point along AB line segment.<br> * If >1 , ptC point will be located on extension of AB<br> * If <0 , ptC point will be located on extension of BA <br> * If >0 && <AB , ptC point will be interior of AB<br> * @return New point ptC coordinates or null if any argument is invalid */ public static Point2D.Double getColinearPointWithRatio(Point2D ptA, Point2D ptB, double k) { if (ptA != null && ptB != null) { return new Point2D.Double(ptB.getX() * k + ptA.getX() * (1 - k), ptB.getY() * k + ptA.getY() * (1 - k)); } return null; } /** * @return median line or null if any argument is invalid */ public static Line2D.Double getMedianLine(Line2D line1, Line2D line2) { if (line1 == null || line2 == null) { return null; } Point2D ptA = line1.getP1(), ptB = line1.getP2(); Point2D ptC = line2.getP1(), ptD = line2.getP2(); Line2D line3 = new Line2D.Double(ptA, ptC); Line2D line4 = new Line2D.Double(ptB, ptD); Point2D ptM, ptN; if (line3.intersectsLine(line4)) { ptM = new Point2D.Double((ptA.getX() + ptD.getX()) / 2, (ptA.getY() + ptD.getY()) / 2); ptN = new Point2D.Double((ptB.getX() + ptC.getX()) / 2, (ptB.getY() + ptC.getY()) / 2); } else { ptM = new Point2D.Double((ptA.getX() + ptC.getX()) / 2, (ptA.getY() + ptC.getY()) / 2); ptN = new Point2D.Double((ptB.getX() + ptD.getX()) / 2, (ptB.getY() + ptD.getY()) / 2); } return new Line2D.Double(ptM, ptN); } /** * @return median line or null if any argument is invalid */ public static Line2D.Double getMedianLine(Point2D ptA, Point2D ptB, Point2D ptC, Point2D ptD) { if (ptA == null || ptB == null || ptC == null || ptD == null) { return null; } return getMedianLine(new Line2D.Double(ptA, ptB), new Line2D.Double(ptC, ptD)); } /** * Let ptA,ptB,ptC,ptD be 2-space position vectors. ....... * * @return null if segment lines are parallel */ public static Point2D.Double getIntersectPoint(Point2D ptA, Point2D ptB, Point2D ptC, Point2D ptD) { if (ptA == null || ptB == null || ptC == null || ptD == null) { return null; } Point2D.Double ptP = null; double denominator = (ptB.getX() - ptA.getX()) * (ptD.getY() - ptC.getY()) - (ptB.getY() - ptA.getY()) * (ptD.getX() - ptC.getX()); if (MathUtil.isDifferentFromZero(denominator)) { double numerator = (ptA.getY() - ptC.getY()) * (ptD.getX() - ptC.getX()) - (ptA.getX() - ptC.getX()) * (ptD.getY() - ptC.getY()); double r = numerator / denominator; ptP = new Point2D.Double(ptA.getX() + r * (ptB.getX() - ptA.getX()), ptA.getY() + r * (ptB.getY() - ptA.getY())); } return ptP; } /** * @return */ public static Point2D getIntersectPoint(Line2D line1, Line2D line2) { if (line1 == null || line2 == null) { return null; } return getIntersectPoint(line1.getP1(), line1.getP2(), line2.getP1(), line2.getP2()); } public static Point2D getIntersectPoint(Line2D line, Rectangle2D rect) { if (line == null || rect == null) { return null; } Point2D p = lineIntersection(line, new Line2D.Double(rect.getMinX(), rect.getMinY(), rect.getMaxX(), rect.getMinY())); if (p == null) { p = lineIntersection(line, new Line2D.Double(rect.getMinX(), rect.getMaxY(), rect.getMaxX(), rect.getMaxY())); if (p == null) { p = lineIntersection(line, new Line2D.Double(rect.getMinX(), rect.getMinY(), rect.getMinX(), rect.getMaxY())); if (p == null) { p = lineIntersection(line, new Line2D.Double(rect.getMaxX(), rect.getMinY(), rect.getMaxX(), rect.getMaxY())); } } } return p; } private static Point2D lineIntersection(Line2D line1, Line2D line2) { if (line1.intersectsLine(line2)) { return GeomUtil.getIntersectPoint(line1, line2); } return null; } /** * @return */ public static boolean lineParallel(Point2D ptA, Point2D ptB, Point2D ptC, Point2D ptD) { if (ptA == null || ptB == null || ptC == null || ptD == null) { throw new IllegalArgumentException("All the points must not be null"); //$NON-NLS-1$ } return MathUtil.isEqualToZero((ptB.getX() - ptA.getX()) * (ptD.getY() - ptC.getY()) - (ptB.getY() - ptA.getY()) * (ptD.getX() - ptC.getX())); } /** * @return */ public static boolean lineColinear(Point2D ptA, Point2D ptB, Point2D ptC, Point2D ptD) { if (lineParallel(ptA, ptB, ptC, ptD)) { return MathUtil.isEqualToZero((ptA.getY() - ptC.getY()) * (ptD.getX() - ptC.getX()) - (ptA.getX() - ptC.getX()) * (ptD.getY() - ptC.getY())); } return false; } /** * @return */ public static Point2D.Double getPerpendicularPointToLine(Point2D ptA, Point2D ptB, Point2D ptC) { if (ptA == null || ptB == null || ptA.equals(ptB) || ptC == null) { return null; } double ax = ptA.getX(), ay = ptA.getY(); double bx = ptB.getX(), by = ptB.getY(); double cx = ptC.getX(), cy = ptC.getY(); double r = ((ay - cy) * (ay - by) + (ax - cx) * (ax - bx)) / Point2D.distanceSq(ax, ay, bx, by); return new Point2D.Double(ax + r * (bx - ax), ay + r * (by - ay)); } public static Point2D.Double getPerpendicularPointToLine(Line2D line, Point2D ptC) { if (line == null || ptC == null) { return null; } return getPerpendicularPointToLine(line.getP1(), line.getP2(), ptC); } /** * Find a point at a given perpendicular distance from a line * * @param ptA * Start of line segment * @param ptB * End of line segment * @param ptP * Point of AB line * @param distPC * Distance from line to return Point ptC <br> * If >0 angle between AB and PC is +90° <br> * If <0 angle between AB and PC is -+90° * @return ptC point */ public static Point2D getPerpendicularPointFromLine(Point2D ptA, Point2D ptB, Point2D ptP, double distPC) { if (ptA == null || ptB == null || ptA.equals(ptB) || ptP == null) { return null; } double distAB = ptA.distance(ptB); double ux = -(ptB.getY() - ptA.getY()) / distAB; double uy = (ptB.getX() - ptA.getX()) / distAB; return new Point2D.Double(ptP.getX() + distPC * ux, ptP.getY() + distPC * uy); } public static Point2D getPerpendicularPointFromLine(Point2D ptA, Point2D ptB, double distAP, double distPC) { return getPerpendicularPointFromLine(ptA, ptB, getColinearPointWithLength(ptA, ptB, distAP), distPC); } /** * * @param ptA * Start of line segment * @param ptB * End of line segment * @param dist * Distance from AB line to the parallel CD line <br> * If >0 angle between AB and AC is +90° <br> * If <0 angle between AB and AC is -+90° * @return */ public static Line2D getParallelLine(Point2D ptA, Point2D ptB, double dist) { if (ptA == null || ptB == null || ptA.equals(ptB)) { return null; } double distAB2 = ptA.distanceSq(ptB); double ux = -(ptB.getY() - ptA.getY()) / distAB2; double uy = (ptB.getX() - ptA.getX()) / distAB2; Point2D ptC = new Point2D.Double(ptA.getX() + dist * ux, ptA.getY() + dist * uy); Point2D ptD = new Point2D.Double(ptB.getX() + dist * ux, ptB.getY() + dist * uy); return new Line2D.Double(ptC, ptD); } /** * * @param ptList * @return */ public static Point2D.Double getCircleCenter(List<Point2D.Double> ptList) { if (ptList == null) { return null; } switch (ptList.size()) { case 3: return getCircleCenter(ptList.get(0), ptList.get(1), ptList.get(2)); case 2: return new Point2D.Double((ptList.get(0).getX() + ptList.get(1).getX()) / 2.0, (ptList.get(0).getY() + ptList.get(1).getY()) / 2.0); default: return null; } } /** * @return */ public static Point2D.Double getCircleCenter(Point2D ptA, Point2D ptB, Point2D ptC) { if (ptA == null || ptB == null || ptC == null) { return null; } double ax = ptA.getX(); double ay = ptA.getY(); double bx = ptB.getX(); double by = ptB.getY(); double cx = ptC.getX(); double cy = ptC.getY(); double c1 = bx - ax; double c2 = by - ay; double c3 = cx - ax; double c4 = cy - ay; double c5 = c1 * (ax + bx) + c2 * (ay + by); double c6 = c3 * (ax + cx) + c4 * (ay + cy); double denom = 2 * (c1 * (cy - by) - c2 * (cx - bx)); if (MathUtil.isEqualToZero(denom)) { return null; // a, b, c must be collinear } double px = (c4 * c5 - c2 * c6) / denom; double py = (c1 * c6 - c3 * c5) / denom; return new Point2D.Double(px, py); } /** * Extract scaling from AffineTransform<br> * Let assume that the AffineTransform is a composite of scales, translates, and rotates. <br> * No independent shear has to be applied and scaling must be uniform along the two axes. * * @param transform * current AffineTransform */ public static double extractScalingFactor(AffineTransform transform) { double scalingFactor = 1.0; if (transform != null) { double sx = transform.getScaleX(); double shx = transform.getShearX(); if (MathUtil.isDifferentFromZero(sx) || MathUtil.isDifferentFromZero(shx)) { scalingFactor = Math.sqrt(sx * sx + shx * shx); } } return scalingFactor; } /** * Extract rotation Angle from a given AffineTransform Matrix.<br> * This function handle cases of mirror image flip about some axis. This changes right handed coordinate system into * a left handed system. Hence, returned angle has an opposite value. * * @param transform * @return angle in the range of [ -PI ; PI ] */ public static double extractAngleRad(AffineTransform transform) { double angleRad = 0.0; if (transform != null) { double sinTheta = transform.getShearY(); double cosTheta = transform.getScaleX(); angleRad = Math.atan2(sinTheta, cosTheta); if ((transform.getType() & AffineTransform.TYPE_FLIP) != 0) { angleRad *= -1.0; } } return angleRad; } /** * * Do a scaling transformation around the anchor point * * @param shape * @param scalingFactor * @param anchorPoint * can be null * @return null if either shape is null or scaling factor is zero */ public static Shape getScaledShape(final Shape shape, double scalingFactor, Point2D anchorPoint) { if (shape == null || MathUtil.isEqualToZero(scalingFactor)) { return null; } AffineTransform scaleTransform = new AffineTransform(); // Identity transformation. if (MathUtil.isDifferent(scalingFactor, 1.0)) { if (anchorPoint != null) { scaleTransform.translate(anchorPoint.getX(), anchorPoint.getY()); } scaleTransform.scale(scalingFactor, scalingFactor); if (anchorPoint != null) { scaleTransform.translate(-anchorPoint.getX(), -anchorPoint.getY()); } } return scaleTransform.createTransformedShape(shape); } /** * @return */ public static Rectangle2D getScaledRectangle(final Rectangle2D rect, double scalingFactor) { Rectangle2D newRect = null; if (rect != null && MathUtil.isDifferent(scalingFactor, 1.0)) { double resizedWidth = rect.getWidth() * scalingFactor; double resizedHeight = rect.getHeight() * scalingFactor; newRect = (Rectangle2D) rect.clone(); newRect.setRect(rect.getX(), rect.getY(), resizedWidth, resizedHeight); } return newRect; } /** * @return */ public static Shape getCornerShape(Point2D ptA, Point2D ptO, Point2D ptB, double cornerSize) { if (ptA == null || ptO == null || ptB == null || ptA.equals(ptO) || ptB.equals(ptO)) { return null; } Point2D ptI1 = GeomUtil.getColinearPointWithLength(ptO, ptA, cornerSize); Point2D ptI2 = GeomUtil.getColinearPointWithLength(ptO, ptB, cornerSize); double rotSignum = Math.signum(GeomUtil.getSmallestRotationAngleDeg(GeomUtil.getAngleDeg(ptB, ptO, ptA))); Point2D ptI3 = GeomUtil.getPerpendicularPointFromLine(ptO, ptA, ptI1, rotSignum * cornerSize); Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO, 3); path.moveTo(ptI1.getX(), ptI1.getY()); path.lineTo(ptI3.getX(), ptI3.getY()); path.lineTo(ptI2.getX(), ptI2.getY()); return path; } public static Shape getArrowShape(Point2D ptO, Point2D ptB, double length, double width) { if (ptO == null || ptB == null || ptB.equals(ptO)) { return null; } Point2D ptI2 = GeomUtil.getColinearPointWithLength(ptO, ptB, length); Point2D ptI3 = GeomUtil.getPerpendicularPointFromLine(ptO, ptB, ptI2, width / 2.0); Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO, 5); path.moveTo(ptI2.getX(), ptI2.getY()); path.lineTo(ptI3.getX(), ptI3.getY()); path.lineTo(ptO.getX(), ptO.getY()); path.lineTo(ptI2.getX() - (ptI3.getX() - ptI2.getX()), ptI2.getY() - (ptI3.getY() - ptI2.getY())); path.lineTo(ptI2.getX(), ptI2.getY()); return path; } /** * @return */ public static Rectangle2D getGrowingRectangle(Rectangle2D rect, double growingSize) { Rectangle2D growingRect = rect != null ? (Rectangle2D) rect.clone() : null; growRectangle(growingRect, growingSize); return growingRect; } /** * @param growingSize */ public static void growRectangle(Rectangle2D rect, double growingSize) { if (rect == null) { return; } if (MathUtil.isDifferentFromZero(growingSize)) { double newX = rect.getX() - growingSize; double newY = rect.getY() - growingSize; double newWidth = rect.getWidth() + (2.0 * growingSize); double newHeight = rect.getHeight() + (2.0 * growingSize); rect.setRect(newX, newY, newWidth, newHeight); } } }