/******************************************************************************* * 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.ui.model.utils.algo; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.weasis.core.api.gui.util.MathUtil; public class MinimumEnclosingRectangle { private final List<Point2D.Double> points; private final boolean isConvex; private List<Point2D.Double> cvxPts = null; private Line2D.Double minDistSeg = new Line2D.Double(); private Point2D.Double minDistPt = null; private double minDist = 0.0; public MinimumEnclosingRectangle(List<Point2D.Double> points) { this(points, false); } public MinimumEnclosingRectangle(List<java.awt.geom.Point2D.Double> points, boolean isConvex) { this.points = points; this.isConvex = isConvex; } private void computeWidthConvex(List<Point2D.Double> pts) { this.cvxPts = pts; int size = cvxPts.size(); if (size == 0) { minDist = 0.0; minDistPt = null; minDistSeg = null; } else if (size == 1) { minDist = 0.0; minDistPt = cvxPts.get(0); minDistSeg.setLine(minDistPt.x, minDistPt.y, minDistPt.x, minDistPt.y); } else if (size == 2 || size == 3) { minDist = 0.0; minDistPt = cvxPts.get(0); Point2D.Double p2 = cvxPts.get(1); minDistSeg.setLine(minDistPt.x, minDistPt.y, p2.x, p2.y); } else { searchRingMinDiameter(cvxPts); } } private void searchRingMinDiameter(List<Point2D.Double> pts) { minDist = Double.MAX_VALUE; int curIndex = 1; for (int i = 0; i < pts.size() - 1; i++) { curIndex = findMaxPerpendicularDistanceFromAB(pts, pts.get(i), pts.get(i + 1), curIndex); } } private int findMaxPerpendicularDistanceFromAB(List<Point2D.Double> pts, Point2D.Double ptA, Point2D.Double ptB, int startIndex) { double maxDist = distanceFromAB(pts.get(startIndex), ptA, ptB); double nextDist = maxDist; int maxIndex = startIndex; int nextIndex = maxIndex; while (nextDist >= maxDist) { maxDist = nextDist; maxIndex = nextIndex; nextIndex = nextIndex(pts, maxIndex); nextDist = distanceFromAB(pts.get(nextIndex), ptA, ptB); } if (maxDist < minDist) { minDist = maxDist; minDistPt = pts.get(maxIndex); minDistSeg = new Line2D.Double(ptA, ptB); } return maxIndex; } private static int nextIndex(List<Point2D.Double> pts, int index) { int i = index + 1; return i >= pts.size() ? 0 : i; } public static double distanceFromAB(Point2D.Double p, Point2D.Double a, Point2D.Double b) { double len2 = (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y); double s = ((a.y - p.y) * (b.x - a.x) - (a.x - p.x) * (b.y - a.y)) / len2; return Math.abs(s) * Math.sqrt(len2); } /** * Gets the minimum rectangle enclosing the points * * @return the minimum rectangle */ public List<Point2D.Double> getMinimumRectangle() { if (minDistPt == null) { if (isConvex) { computeWidthConvex(points); } else { List<java.awt.geom.Point2D.Double> convexPts = (new ConvexHull(points)).getConvexHull(); computeWidthConvex(convexPts); } } if (MathUtil.isEqualToZero(minDist)) { return Collections.emptyList(); } double dx = minDistSeg.getX2() - minDistSeg.getX1(); double dy = minDistSeg.getY2() - minDistSeg.getY1(); double minPara = Double.MAX_VALUE; double maxPara = -Double.MAX_VALUE; double minPerp = Double.MAX_VALUE; double maxPerp = -Double.MAX_VALUE; // compute maxima and minima of lines parallel and perpendicular to AB segment for (int i = 0; i < cvxPts.size(); i++) { double paraC = getC(dx, dy, cvxPts.get(i)); if (paraC > maxPara) { maxPara = paraC; } if (paraC < minPara) { minPara = paraC; } double perpC = getC(-dy, dx, cvxPts.get(i)); if (perpC > maxPerp) { maxPerp = perpC; } if (perpC < minPerp) { minPerp = perpC; } } Line2D.Double maxPerpLine = getLine(-dx, -dy, maxPerp); Line2D.Double minPerpLine = getLine(-dx, -dy, minPerp); Line2D.Double maxParaLine = getLine(-dy, dx, maxPara); Line2D.Double minParaLine = getLine(-dy, dx, minPara); List<Point2D.Double> rect = new ArrayList<>(); rect.add(intersection(maxParaLine.getP1(), maxParaLine.getP2(), maxPerpLine.getP1(), maxPerpLine.getP2())); rect.add(intersection(minParaLine.getP1(), minParaLine.getP2(), maxPerpLine.getP1(), maxPerpLine.getP2())); rect.add(intersection(minParaLine.getP1(), minParaLine.getP2(), minPerpLine.getP1(), minPerpLine.getP2())); rect.add(intersection(maxParaLine.getP1(), maxParaLine.getP2(), minPerpLine.getP1(), minPerpLine.getP2())); return rect; } /** * Intersection point between two line segments. */ public static Point2D.Double intersection(Point2D u1, Point2D u2, Point2D w1, Point2D w2) { double ux = u1.getY() - u2.getY(); double uy = u2.getX() - u1.getX(); double ul = u1.getX() * u2.getY() - u2.getX() * u1.getY(); double wx = w1.getY() - w2.getY(); double wy = w2.getX() - w1.getX(); double wl = w1.getX() * w2.getY() - w2.getX() * w1.getY(); double x = uy * wl - wy * ul; double y = wx * ul - ux * wl; double l = ux * wy - wx * uy; double xNorm = x / l; double yNorm = y / l; if ((Double.isNaN(xNorm)) || (Double.isInfinite(xNorm) || Double.isNaN(yNorm)) || (Double.isInfinite(yNorm))) { return null; } return new Point2D.Double(xNorm, yNorm); } private static double getC(double a, double b, Point2D.Double p) { return a * p.y - b * p.x; } private static Line2D.Double getLine(double a, double b, double c) { // Line equation: ax + by = c if (Math.abs(b) > Math.abs(a)) { return new Line2D.Double(0.0, c / b, 1.0, c / b - a / b); } else { return new Line2D.Double(c / a, 0.0, c / a - b / a, 1.0); } } }