/* * Copyright (C) 2004 The Concord Consortium, Inc., * 10 Concord Crossing, Concord, MA 01742 * * Web Site: http://www.concord.org * Email: info@concord.org * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * END LICENSE */ /** * <p>Title: QuickHull</p> * @author Dmitry Markman, dima@concord.org * @version 1.0 */ package org.concord.swing; import java.awt.Point; import java.util.Vector; /** * class that implements QuickHull algorithm to construct convex hull polygon<br> * usage example: * <blockquote><pre> * int nDots = 500; * int offset = 50; * int sizeX = 400; * int sizeY = 400; * Point []dots = new Point[nDots]; * for(int i =0; i < dots.length; i++){ * int px = (int)Math.round(offset + (sizeX - 2*offset)*Math.random()); * int py = (int)Math.round(offset + (sizeY - 2*offset)*Math.random()); * dots[i] = new Point(px,py); * } * qh = new QuickHull(dots); * Point []dots = qh.getOriginalPoints(); * Vector outPoints = qh.getHullPointsAsVector(); * </pre></blockquote> */ public class QuickHull{ Point []originalPoints; int fullSteps = 0; Vector hullPoints = new Vector(); /* * constructor for <code>QuickHull</code> class * @param originalPoints {@link Point}[] initial points */ public QuickHull(Point []originalPoints){ this.originalPoints = originalPoints; qhull(originalPoints,0,0); reorderPoints(hullPoints); } /** * Returns original {@link Point} array. * @return original {@link Point} array */ public Point []getOriginalPoints(){ return originalPoints; } /** * Returns convex hull points as {@link Vector}. * @return convex hull points as {@link Vector}. */ public Vector getHullPointsAsVector(){ return (Vector)hullPoints.clone(); } /** * Returns convex hull points as {@link Point}[]. * @return convex hull points as {@link Point}[]. */ public Point []getHullPointsAsArray(){ if(hullPoints == null) return null; Point []hulldots = new Point[hullPoints.size()]; for(int i = 0; i < hulldots.length; i++){ hulldots[i] = (Point)hullPoints.elementAt(i); } return hulldots; } void reorderPoints(Vector v){ AngleWrapper []angleWrappers = new AngleWrapper[v.size()]; double xc = 0; double yc = 0; for(int i = 0; i < v.size(); i++){ Point pt = (Point)v.elementAt(i); xc += pt.x; yc += pt.y; } xc /= v.size(); yc /= v.size(); for(int i = 0; i < angleWrappers.length; i++){ angleWrappers[i] = createAngleWrapper((Point)v.elementAt(i),xc,yc); } java.util.Arrays.sort(angleWrappers,new AngleComparator()); v.removeAllElements(); for(int i = 0; i < angleWrappers.length; i++){ v.add(angleWrappers[i].pt); } } void qhull(Object []dots0,int up,int step){ fullSteps++; if(dots0 == null || dots0.length < 1 || step > 200) return; if(dots0.length < 2){ addHullPoint((Point)dots0[0]); return; } try{ int leftIndex = 0; int rightIndex = 0; for(int i = 1; i < dots0.length; i++){ if(((Point)dots0[i]).x < ((Point)dots0[leftIndex]).x){ leftIndex = i; } if(((Point)dots0[i]).x > ((Point)dots0[rightIndex]).x){ rightIndex = i; } } Point leftPoint = (Point)dots0[leftIndex]; Point rightPoint = (Point)dots0[rightIndex]; addHullPoint(leftPoint); addHullPoint(rightPoint); if(dots0.length == 3){ int middlePoint = -1; for(int i = 0; i < dots0.length; i++){ if(i == leftIndex || i == rightIndex) continue; middlePoint = i; break; } addHullPoint((Point)dots0[middlePoint]); }else if(dots0.length > 3){ Vector vIn = new Vector(); Vector vOut = new Vector(); if(up >= 0){ int upIndex = selectPoints(dots0,leftPoint,rightPoint,true,vIn); if(upIndex >= 0 && vIn.size() > 0){ Point upPoint = (Point)vIn.elementAt(upIndex); vOut.removeAllElements(); selectPoints(vIn,leftPoint,upPoint,true,vOut); qhull(vOut.toArray(),1,step+1); vOut.removeAllElements(); selectPoints(vIn,upPoint,rightPoint,true,vOut); qhull(vOut.toArray(),1,step+1); } } if(up <= 0){ vIn.removeAllElements(); int downIndex = selectPoints(dots0,rightPoint,leftPoint,false,vIn); if(downIndex >= 0 && vIn.size() > 0){ Point downPoint = (Point)vIn.elementAt(downIndex); vOut.removeAllElements(); selectPoints(vIn,rightPoint,downPoint,false,vOut); qhull(vOut.toArray(),-1,step+1); vOut.removeAllElements(); selectPoints(vIn,downPoint,leftPoint,false,vOut); qhull(vOut.toArray(),-1,step+1); } } } }catch(Throwable t){} } void addHullPoint(Point pt){ if(!hullPoints.contains(pt)) hullPoints.add(pt); } static int selectPoints(Object []pIn,Point pLeft,Point pRight,boolean up,Vector vOut){ int retValue = -1; if(pIn == null || vOut == null) return retValue; double k = (double)(pRight.y - pLeft.y)/(double)(pRight.x - pLeft.x); double A = -k; double B = 1; double C = k*pLeft.x-pLeft.y; double dup = 0; for(int i = 0; i < pIn.length; i++){ Point pt = (Point)pIn[i]; if(pt.equals(pLeft) || pt.equals(pRight)) continue; int px = pt.x; int py = pt.y; double y = pLeft.y + k*(px - pLeft.x); if((!up && y < py) || (up && y > py)){ vOut.add(pt); double d = (A*px+B*py+C); if(d < 0) d = -d; if(d > dup){ dup = d; retValue = vOut.size() - 1; } } } vOut.add(pLeft); vOut.add(pRight); return retValue; } static int selectPoints(Vector vIn,Point pLeft,Point pRight,boolean up,Vector vOut){ int retValue = -1; if(vIn == null || vOut == null) return retValue; double k = (double)(pRight.y - pLeft.y)/(double)(pRight.x - pLeft.x); double A = -k; double B = 1; double C = k*pLeft.x-pLeft.y; double dup = 0; for(int i = 0; i < vIn.size(); i++){ Point pt = (Point)vIn.elementAt(i); if(pt.equals(pLeft) || pt.equals(pRight)) continue; int px = pt.x; int py = pt.y; double y = pLeft.y + k*(px - pLeft.x); if((!up && y < py) || (up && y > py)){ vOut.add(pt); double d = (A*px+B*py+C); if(d < 0) d = -d; if(d > dup){ dup = d; retValue = vOut.size() - 1; } } } vOut.add(pLeft); vOut.add(pRight); return retValue; } static AngleWrapper createAngleWrapper(Point pt,double xc,double yc){ double angle = Math.atan2(pt.y-yc,pt.x-xc); if(angle < 0) angle += 2*Math.PI; return new AngleWrapper(angle,new Point(pt)); } static class AngleComparator implements java.util.Comparator{ public int compare(Object obj1,Object obj2){ if(!(obj1 instanceof AngleWrapper) || !(obj2 instanceof AngleWrapper)) return 0; AngleWrapper ac1 = (AngleWrapper)obj1; AngleWrapper ac2 = (AngleWrapper)obj2; return (ac1.angle < ac2.angle)?-1:1; } } static class AngleWrapper implements Comparable{ double angle; Point pt; AngleWrapper(double angle,Point pt){ this.angle = angle; this.pt = pt; } public int compareTo(Object obj){ if(!(obj instanceof AngleWrapper)) return 0; AngleWrapper ac = (AngleWrapper)obj; return (ac.angle < angle)?-1:1; } } static int nDots = 500; static int offset = 50; static int sizeX = 400; static int sizeY = 400; static double r = (double)sizeX/2 - offset; static double xc = (double)sizeX / 2; static double yc = (double)sizeY / 2; static QuickHull qh; static void initDots(){ Point []dots = new Point[nDots]; for(int i =0; i < dots.length; i++){ int px = (int)Math.round(offset + (sizeX - 2*offset)*Math.random()); int py = (int)Math.round(offset + (sizeY - 2*offset)*Math.random()); // double angle = (double)i*Math.PI*2/(double)dots.length; // int px = (int)Math.round(xc + r*Math.cos(angle)); // int py = (int)Math.round(yc + r*Math.sin(angle)); dots[i] = new Point(px,py); } qh = new QuickHull(dots); System.out.println("hullPoints "+qh.hullPoints.size()+" fullSteps "+qh.fullSteps); } static void drawDots(java.awt.Graphics g){ if(qh == null) return; g.setColor(java.awt.Color.gray); Point []dots = qh.getOriginalPoints(); for(int i = 0; i < dots.length; i++){ Point pt = dots[i]; g.fillRect(pt.x,pt.y,3,3); } g.setColor(java.awt.Color.red); Vector outPoints = qh.getHullPointsAsVector(); for(int i = 0; i < outPoints.size(); i++){ Point pt = (Point)outPoints.elementAt(i); g.fillRect(pt.x,pt.y,3,3); if(i > 0){ Point ptPrev = (Point)outPoints.elementAt(i-1); g.drawLine(ptPrev.x,ptPrev.y,pt.x,pt.y); } if(i == outPoints.size() - 1){ Point ptPrev = (Point)outPoints.elementAt(0); g.drawLine(ptPrev.x,ptPrev.y,pt.x,pt.y); } } } public static void main(String args[]){ javax.swing.JFrame frame1 = new javax.swing.JFrame("test"){ boolean wasInited = false; public void paint(java.awt.Graphics g){ super.paint(g); if(!wasInited){ initDots(); wasInited = true; } drawDots(g); } }; frame1.setSize(sizeX,sizeY); frame1.setVisible(true); } }