13 package eu.mihosoft.vrl.v3d.ext.quickhull3d;
121 public static final int CLOCKWISE = 0x1;
127 public static final int INDEXED_FROM_ONE = 0x2;
133 public static final int INDEXED_FROM_ZERO = 0x4;
139 public static final int POINT_RELATIVE = 0x8;
145 public static final double AUTOMATIC_TOLERANCE = -1;
148 protected int findIndex = -1;
152 protected double charLength;
155 protected boolean debug =
false;
158 protected Vertex[] pointBuffer =
new Vertex[0];
161 protected int[] vertexPointIndices =
new int[0];
164 private Face[] discardedFaces =
new Face[3];
167 private Vertex[] maxVtxs =
new Vertex[3];
170 private Vertex[] minVtxs =
new Vertex[3];
173 protected Vector faces =
new Vector(16);
176 protected Vector horizon =
new Vector(16);
179 private FaceList newFaces =
new FaceList();
182 private VertexList unclaimed =
new VertexList();
185 private VertexList claimed =
new VertexList();
188 protected int numVertices;
191 protected int numFaces;
194 protected int numPoints;
197 protected double explicitTolerance = AUTOMATIC_TOLERANCE;
200 protected double tolerance;
208 public boolean getDebug()
218 public void setDebug (
boolean enable)
226 static private final double DOUBLE_PREC = 2.2204460492503131e-16;
241 public double getDistanceTolerance()
255 public void setExplicitDistanceTolerance(
double tol)
257 explicitTolerance = tol;
266 public double getExplicitDistanceTolerance()
268 return explicitTolerance;
277 private void addPointToFace (Vertex vtx, Face face)
281 if (face.outside ==
null)
285 { claimed.insertBefore (vtx, face.outside);
296 private void removePointFromFace (Vertex vtx, Face face)
298 if (vtx == face.outside)
299 {
if (vtx.next !=
null && vtx.next.face == face)
300 { face.outside = vtx.next;
303 { face.outside =
null;
306 claimed.delete (vtx);
315 private Vertex removeAllPointsFromFace (Face face)
317 if (face.outside !=
null)
319 Vertex end = face.outside;
320 while (end.next !=
null && end.next.face == face)
323 claimed.delete (face.outside, end);
335 public QuickHull3D ()
351 public QuickHull3D (
double[] coords)
352 throws IllegalArgumentException
354 build (coords, coords.length/3);
366 public QuickHull3D (Point3d[] points)
367 throws IllegalArgumentException
369 build (points, points.length);
379 private HalfEdge findHalfEdge (Vertex tail, Vertex head)
382 for (Iterator it=faces.iterator(); it.hasNext(); )
383 { HalfEdge he = ((Face)it.next()).findEdge (tail, head);
399 protected void setHull (
double[] coords,
int nump,
400 int[][] faceIndices,
int numf)
403 setPoints (coords, nump);
405 for (
int i=0; i<numf; i++)
406 { Face face = Face.create (pointBuffer, faceIndices[i]);
407 HalfEdge he = face.he0;
409 { HalfEdge heOpp = findHalfEdge (he.head(), he.tail());
411 { he.setOpposite (heOpp);
415 while (he != face.he0);
426 private void printQhullErrors (Process proc)
429 boolean wrote =
false;
430 InputStream es = proc.getErrorStream();
431 while (es.available() > 0)
432 { System.out.write (es.read());
436 { System.out.println(
"");
447 protected void setFromQhull (
double[] coords,
int nump,
450 String commandStr =
"./qhull i";
452 { commandStr +=
" -Qt";
456 Process proc = Runtime.getRuntime().exec (commandStr);
457 PrintStream ps =
new PrintStream (proc.getOutputStream());
458 StreamTokenizer stok =
459 new StreamTokenizer (
460 new InputStreamReader (proc.getInputStream()));
462 ps.println (
"3 " + nump);
463 for (
int i=0; i<nump; i++)
465 coords[i*3+0] +
" " +
466 coords[i*3+1] +
" " +
471 Vector indexList =
new Vector(3);
472 stok.eolIsSignificant(
true);
473 printQhullErrors (proc);
478 while (stok.sval ==
null ||
479 !stok.sval.startsWith (
"MERGEexact"));
480 for (
int i=0; i<4; i++)
483 if (stok.ttype != StreamTokenizer.TT_NUMBER)
484 { System.out.println (
"Expecting number of faces");
487 int numf = (int)stok.nval;
489 int[][] faceIndices =
new int[numf][];
490 for (
int i=0; i<numf; i++)
492 while (stok.nextToken() != StreamTokenizer.TT_EOL)
493 {
if (stok.ttype != StreamTokenizer.TT_NUMBER)
494 { System.out.println (
"Expecting face index");
497 indexList.add (0,
new Integer((
int)stok.nval));
499 faceIndices[i] =
new int[indexList.size()];
501 for (Iterator it=indexList.iterator(); it.hasNext(); )
502 { faceIndices[i][k++] = ((Integer)it.next()).intValue();
505 setHull (coords, nump, faceIndices, numf);
508 { e.printStackTrace();
518 private void printPoints (PrintStream ps)
520 for (
int i=0; i<numPoints; i++)
521 { Point3d pnt = pointBuffer[i].pnt;
522 ps.println (pnt.x +
", " + pnt.y +
", " + pnt.z +
",");
537 public void build (
double[] coords)
538 throws IllegalArgumentException
540 build (coords, coords.length/3);
556 public void build (
double[] coords,
int nump)
557 throws IllegalArgumentException
560 {
throw new IllegalArgumentException (
561 "Less than four input points specified");
563 if (coords.length/3 < nump)
564 {
throw new IllegalArgumentException (
565 "Coordinate array too small for specified number of points");
568 setPoints (coords, nump);
580 public void build (Point3d[] points)
581 throws IllegalArgumentException
583 build (points, points.length);
595 public void build (Point3d[] points,
int nump)
596 throws IllegalArgumentException
599 {
throw new IllegalArgumentException (
600 "Less than four input points specified");
602 if (points.length < nump)
603 {
throw new IllegalArgumentException (
604 "Point array too small for specified number of points");
607 setPoints (points, nump);
617 public void triangulate ()
619 double minArea = 1000*charLength*DOUBLE_PREC;
621 for (Iterator it=faces.iterator(); it.hasNext(); )
622 { Face face = (Face)it.next();
623 if (face.mark == Face.VISIBLE)
625 face.triangulate (newFaces, minArea);
629 for (Face face=newFaces.first(); face!=
null; face=face.next)
649 protected void initBuffers (
int nump)
651 if (pointBuffer.length < nump)
652 { Vertex[] newBuffer =
new Vertex[nump];
653 vertexPointIndices =
new int[nump];
654 for (
int i=0; i<pointBuffer.length; i++)
655 { newBuffer[i] = pointBuffer[i];
657 for (
int i=pointBuffer.length; i<nump; i++)
658 { newBuffer[i] =
new Vertex();
660 pointBuffer = newBuffer;
674 protected void setPoints (
double[] coords,
int nump)
676 for (
int i=0; i<nump; i++)
678 Vertex vtx = pointBuffer[i];
679 vtx.pnt.set (coords[i*3+0], coords[i*3+1], coords[i*3+2]);
690 protected void setPoints (Point3d[] pnts,
int nump)
692 for (
int i=0; i<nump; i++)
694 Vertex vtx = pointBuffer[i];
695 vtx.pnt.set (pnts[i]);
703 protected void computeMaxAndMin ()
705 Vector3d max =
new Vector3d();
706 Vector3d min =
new Vector3d();
708 for (
int i=0; i<3; i++)
709 { maxVtxs[i] = minVtxs[i] = pointBuffer[0];
711 max.set (pointBuffer[0].pnt);
712 min.set (pointBuffer[0].pnt);
714 for (
int i=1; i<numPoints; i++)
715 { Point3d pnt = pointBuffer[i].pnt;
718 maxVtxs[0] = pointBuffer[i];
720 else if (pnt.x < min.x)
722 minVtxs[0] = pointBuffer[i];
726 maxVtxs[1] = pointBuffer[i];
728 else if (pnt.y < min.y)
730 minVtxs[1] = pointBuffer[i];
734 maxVtxs[2] = pointBuffer[i];
736 else if (pnt.z < min.z)
738 minVtxs[2] = pointBuffer[i];
744 charLength = Math.max(max.x-min.x, max.y-min.y);
745 charLength = Math.max(max.z-min.z, charLength);
746 if (explicitTolerance == AUTOMATIC_TOLERANCE)
748 3*DOUBLE_PREC*(Math.max(Math.abs(max.x),Math.abs(min.x))+
749 Math.max(Math.abs(max.y),Math.abs(min.y))+
750 Math.max(Math.abs(max.z),Math.abs(min.z)));
753 { tolerance = explicitTolerance;
762 protected void createInitialSimplex ()
763 throws IllegalArgumentException
768 for (
int i=0; i<3; i++)
769 {
double diff = maxVtxs[i].pnt.get(i)-minVtxs[i].pnt.get(i);
776 if (max <= tolerance)
777 {
throw new IllegalArgumentException (
778 "Input points appear to be coincident");
780 Vertex[] vtx =
new Vertex[4];
784 vtx[0] = maxVtxs[imax];
785 vtx[1] = minVtxs[imax];
789 Vector3d u01 =
new Vector3d();
790 Vector3d diff02 =
new Vector3d();
791 Vector3d nrml =
new Vector3d();
792 Vector3d xprod =
new Vector3d();
794 u01.sub (vtx[1].pnt, vtx[0].pnt);
796 for (
int i=0; i<numPoints; i++)
797 { diff02.sub (pointBuffer[i].pnt, vtx[0].pnt);
798 xprod.cross (u01, diff02);
799 double lenSqr = xprod.normSquared();
800 if (lenSqr > maxSqr &&
801 pointBuffer[i] != vtx[0] &&
802 pointBuffer[i] != vtx[1])
804 vtx[2] = pointBuffer[i];
808 if (Math.sqrt(maxSqr) <= 100*tolerance)
809 {
throw new IllegalArgumentException (
810 "Input points appear to be colinear");
816 double d0 = vtx[2].pnt.dot (nrml);
817 for (
int i=0; i<numPoints; i++)
818 {
double dist = Math.abs (pointBuffer[i].pnt.dot(nrml) - d0);
819 if (dist > maxDist &&
820 pointBuffer[i] != vtx[0] &&
821 pointBuffer[i] != vtx[1] &&
822 pointBuffer[i] != vtx[2])
824 vtx[3] = pointBuffer[i];
827 if (Math.abs(maxDist) <= 100*tolerance)
828 {
throw new IllegalArgumentException (
829 "Input points appear to be coplanar");
833 { System.out.println (
"initial vertices:");
834 System.out.println (vtx[0].index +
": " + vtx[0].pnt);
835 System.out.println (vtx[1].index +
": " + vtx[1].pnt);
836 System.out.println (vtx[2].index +
": " + vtx[2].pnt);
837 System.out.println (vtx[3].index +
": " + vtx[3].pnt);
840 Face[] tris =
new Face[4];
842 if (vtx[3].pnt.dot (nrml) - d0 < 0)
843 { tris[0] = Face.createTriangle (vtx[0], vtx[1], vtx[2]);
844 tris[1] = Face.createTriangle (vtx[3], vtx[1], vtx[0]);
845 tris[2] = Face.createTriangle (vtx[3], vtx[2], vtx[1]);
846 tris[3] = Face.createTriangle (vtx[3], vtx[0], vtx[2]);
848 for (
int i=0; i<3; i++)
850 tris[i+1].getEdge(1).setOpposite (tris[k+1].getEdge(0));
851 tris[i+1].getEdge(2).setOpposite (tris[0].getEdge(k));
855 { tris[0] = Face.createTriangle (vtx[0], vtx[2], vtx[1]);
856 tris[1] = Face.createTriangle (vtx[3], vtx[0], vtx[1]);
857 tris[2] = Face.createTriangle (vtx[3], vtx[1], vtx[2]);
858 tris[3] = Face.createTriangle (vtx[3], vtx[2], vtx[0]);
860 for (
int i=0; i<3; i++)
862 tris[i+1].getEdge(0).setOpposite (tris[k+1].getEdge(1));
863 tris[i+1].getEdge(2).setOpposite (tris[0].getEdge((3-i)%3));
868 for (
int i=0; i<4; i++)
869 { faces.add (tris[i]);
872 for (
int i=0; i<numPoints; i++)
873 { Vertex v = pointBuffer[i];
875 if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3])
881 for (
int k=0; k<4; k++)
882 {
double dist = tris[k].distanceToPlane (v.pnt);
889 { addPointToFace (v, maxFace);
899 public int getNumVertices()
911 public Point3d[] getVertices()
913 Point3d[] vtxs =
new Point3d[numVertices];
914 for (
int i=0; i<numVertices; i++)
915 { vtxs[i] = pointBuffer[vertexPointIndices[i]].pnt;
930 public int getVertices(
double[] coords)
932 for (
int i=0; i<numVertices; i++)
933 { Point3d pnt = pointBuffer[vertexPointIndices[i]].pnt;
934 coords[i*3+0] = pnt.x;
935 coords[i*3+1] = pnt.y;
936 coords[i*3+2] = pnt.z;
947 public int[] getVertexPointIndices()
949 int[] indices =
new int[numVertices];
950 for (
int i=0; i<numVertices; i++)
951 { indices[i] = vertexPointIndices[i];
961 public int getNumFaces()
982 public int[][] getFaces ()
1004 public int[][] getFaces (
int indexFlags)
1006 int[][] allFaces =
new int[faces.size()][];
1008 for (Iterator it=faces.iterator(); it.hasNext(); )
1009 { Face face = (Face)it.next();
1010 allFaces[k] =
new int[face.numVertices()];
1011 getFaceIndices (allFaces[k], face, indexFlags);
1038 public void print (PrintStream ps)
1064 public void print (PrintStream ps,
int indexFlags)
1066 if ((indexFlags & INDEXED_FROM_ZERO) == 0)
1067 { indexFlags |= INDEXED_FROM_ONE;
1069 for (
int i=0; i<numVertices; i++)
1070 { Point3d pnt = pointBuffer[vertexPointIndices[i]].pnt;
1071 ps.println (
"v " + pnt.x +
" " + pnt.y +
" " + pnt.z);
1073 for (Iterator fi=faces.iterator(); fi.hasNext(); )
1074 { Face face = (Face)fi.next();
1075 int[] indices =
new int[face.numVertices()];
1076 getFaceIndices (indices, face, indexFlags);
1079 for (
int k=0; k<indices.length; k++)
1080 { ps.print (
" " + indices[k]);
1093 private void getFaceIndices (
int[] indices, Face face,
int flags)
1095 boolean ccw = ((flags & CLOCKWISE) == 0);
1096 boolean indexedFromOne = ((flags & INDEXED_FROM_ONE) != 0);
1097 boolean pointRelative = ((flags & POINT_RELATIVE) != 0);
1099 HalfEdge hedge = face.he0;
1102 {
int idx = hedge.head().index;
1104 { idx = vertexPointIndices[idx];
1110 hedge = (ccw ? hedge.next : hedge.prev);
1112 while (hedge != face.he0);
1120 protected void resolveUnclaimedPoints (FaceList newFaces)
1122 Vertex vtxNext = unclaimed.first();
1123 for (Vertex vtx=vtxNext; vtx!=
null; vtx=vtxNext)
1124 { vtxNext = vtx.next;
1126 double maxDist = tolerance;
1127 Face maxFace =
null;
1128 for (Face newFace=newFaces.first(); newFace !=
null;
1129 newFace=newFace.next)
1131 if (newFace.mark == Face.VISIBLE)
1132 {
double dist = newFace.distanceToPlane(vtx.pnt);
1137 if (maxDist > 1000*tolerance)
1142 if (maxFace !=
null)
1144 addPointToFace (vtx, maxFace);
1145 if (debug && vtx.index == findIndex)
1146 { System.out.println (findIndex +
" CLAIMED BY " +
1147 maxFace.getVertexString());
1151 {
if (debug && vtx.index == findIndex)
1152 { System.out.println (findIndex +
" DISCARDED");
1164 protected void deleteFacePoints (Face face, Face absorbingFace)
1166 Vertex faceVtxs = removeAllPointsFromFace (face);
1167 if (faceVtxs !=
null)
1169 if (absorbingFace ==
null)
1170 { unclaimed.addAll (faceVtxs);
1173 { Vertex vtxNext = faceVtxs;
1174 for (Vertex vtx=vtxNext; vtx!=
null; vtx=vtxNext)
1175 { vtxNext = vtx.next;
1176 double dist = absorbingFace.distanceToPlane (vtx.pnt);
1177 if (dist > tolerance)
1179 addPointToFace (vtx, absorbingFace);
1183 unclaimed.add (vtx);
1191 private static final int NONCONVEX_WRT_LARGER_FACE = 1;
1194 private static final int NONCONVEX = 2;
1202 protected double oppFaceDistance (HalfEdge he)
1204 return he.face.distanceToPlane (he.opposite.face.getCentroid());
1214 private boolean doAdjacentMerge (Face face,
int mergeType)
1216 HalfEdge hedge = face.he0;
1218 boolean convex =
true;
1220 { Face oppFace = hedge.oppositeFace();
1221 boolean merge =
false;
1222 double dist1, dist2;
1224 if (mergeType == NONCONVEX)
1226 if (oppFaceDistance (hedge) > -tolerance ||
1227 oppFaceDistance (hedge.opposite) > -tolerance)
1235 if (face.area > oppFace.area)
1236 {
if ((dist1 = oppFaceDistance (hedge)) > -tolerance)
1239 else if (oppFaceDistance (hedge.opposite) > -tolerance)
1244 {
if (oppFaceDistance (hedge.opposite) > -tolerance)
1247 else if (oppFaceDistance (hedge) > -tolerance)
1255 { System.out.println (
1256 " merging " + face.getVertexString() +
" and " +
1257 oppFace.getVertexString());
1260 int numd = face.mergeAdjacentFace (hedge, discardedFaces);
1261 for (
int i=0; i<numd; i++)
1262 { deleteFacePoints (discardedFaces[i], face);
1265 { System.out.println (
1266 " result: " + face.getVertexString());
1272 while (hedge != face.he0);
1274 { face.mark = Face.NON_CONVEX;
1287 protected void calculateHorizon (
1288 Point3d eyePnt, HalfEdge edge0, Face face, Vector horizon)
1291 deleteFacePoints (face,
null);
1292 face.mark = Face.DELETED;
1294 { System.out.println (
" visiting face " + face.getVertexString());
1298 { edge0 = face.getEdge(0);
1302 { edge = edge0.getNext();
1305 { Face oppFace = edge.oppositeFace();
1306 if (oppFace.mark == Face.VISIBLE)
1307 {
if (oppFace.distanceToPlane (eyePnt) > tolerance)
1308 { calculateHorizon (eyePnt, edge.getOpposite(),
1312 { horizon.add (edge);
1314 { System.out.println (
" adding horizon edge " +
1315 edge.getVertexString());
1319 edge = edge.getNext();
1321 while (edge != edge0);
1331 private HalfEdge addAdjoiningFace (
1332 Vertex eyeVtx, HalfEdge he)
1334 Face face = Face.createTriangle (
1335 eyeVtx, he.tail(), he.head());
1337 face.getEdge(-1).setOpposite(he.getOpposite());
1338 return face.getEdge(0);
1348 protected void addNewFaces (
1349 FaceList newFaces, Vertex eyeVtx, Vector horizon)
1353 HalfEdge hedgeSidePrev =
null;
1354 HalfEdge hedgeSideBegin =
null;
1356 for (Iterator it=horizon.iterator(); it.hasNext(); )
1357 { HalfEdge horizonHe = (HalfEdge)it.next();
1358 HalfEdge hedgeSide = addAdjoiningFace (eyeVtx, horizonHe);
1360 { System.out.println (
1361 "new face: " + hedgeSide.face.getVertexString());
1363 if (hedgeSidePrev !=
null)
1364 { hedgeSide.next.setOpposite (hedgeSidePrev);
1367 { hedgeSideBegin = hedgeSide;
1369 newFaces.add (hedgeSide.getFace());
1370 hedgeSidePrev = hedgeSide;
1372 hedgeSideBegin.next.setOpposite (hedgeSidePrev);
1380 protected Vertex nextPointToAdd()
1382 if (!claimed.isEmpty())
1383 { Face eyeFace = claimed.first().face;
1384 Vertex eyeVtx =
null;
1386 for (Vertex vtx=eyeFace.outside;
1387 vtx !=
null && vtx.face==eyeFace;
1389 {
double dist = eyeFace.distanceToPlane(vtx.pnt);
1407 protected void addPointToHull(Vertex eyeVtx)
1413 { System.out.println (
"Adding point: " + eyeVtx.index);
1414 System.out.println (
1415 " which is " + eyeVtx.face.distanceToPlane(eyeVtx.pnt) +
1416 " above face " + eyeVtx.face.getVertexString());
1418 removePointFromFace (eyeVtx, eyeVtx.face);
1419 calculateHorizon (eyeVtx.pnt,
null, eyeVtx.face, horizon);
1421 addNewFaces (newFaces, eyeVtx, horizon);
1426 for (Face face = newFaces.first(); face!=
null; face=face.next)
1428 if (face.mark == Face.VISIBLE)
1429 {
while (doAdjacentMerge(face, NONCONVEX_WRT_LARGER_FACE))
1435 for (Face face = newFaces.first(); face!=
null; face=face.next)
1437 if (face.mark == Face.NON_CONVEX)
1438 { face.mark = Face.VISIBLE;
1439 while (doAdjacentMerge(face, NONCONVEX))
1443 resolveUnclaimedPoints(newFaces);
1449 protected void buildHull ()
1454 computeMaxAndMin ();
1455 createInitialSimplex ();
1456 while ((eyeVtx = nextPointToAdd()) !=
null)
1457 { addPointToHull (eyeVtx);
1460 { System.out.println (
"iteration " + cnt +
" done");
1463 reindexFacesAndVertices();
1465 { System.out.println (
"hull done");
1475 private void markFaceVertices (Face face,
int mark)
1477 HalfEdge he0 = face.getFirstEdge();
1480 { he.head().index = mark;
1489 protected void reindexFacesAndVertices()
1491 for (
int i=0; i<numPoints; i++)
1492 { pointBuffer[i].index = -1;
1496 for (Iterator it=faces.iterator(); it.hasNext(); )
1497 { Face face = (Face)it.next();
1498 if (face.mark != Face.VISIBLE)
1502 { markFaceVertices (face, 0);
1508 for (
int i=0; i<numPoints; i++)
1509 { Vertex vtx = pointBuffer[i];
1511 { vertexPointIndices[numVertices] = i;
1512 vtx.index = numVertices++;
1525 protected boolean checkFaceConvexity (
1526 Face face,
double tol, PrintStream ps)
1529 HalfEdge he = face.he0;
1531 { face.checkConsistency();
1533 dist = oppFaceDistance (he);
1536 { ps.println (
"Edge " + he.getVertexString() +
1537 " non-convex by " + dist);
1541 dist = oppFaceDistance (he.opposite);
1544 { ps.println (
"Opposite edge " +
1545 he.opposite.getVertexString() +
1546 " non-convex by " + dist);
1550 if (he.next.oppositeFace() == he.oppositeFace())
1552 { ps.println (
"Redundant vertex " + he.head().index +
1553 " in face " + face.getVertexString());
1559 while (he != face.he0);
1570 protected boolean checkFaces(
double tol, PrintStream ps)
1573 boolean convex =
true;
1574 for (Iterator it=faces.iterator(); it.hasNext(); )
1575 { Face face = (Face)it.next();
1576 if (face.mark == Face.VISIBLE)
1577 {
if (!checkFaceConvexity (face, tol, ps))
1597 public boolean check (PrintStream ps)
1599 return check (ps, getDistanceTolerance());
1622 public boolean check (PrintStream ps,
double tol)
1628 double pointTol = 10*tol;
1630 if (!checkFaces(tolerance, ps))
1636 for (
int i=0; i<numPoints; i++)
1637 { Point3d pnt = pointBuffer[i].pnt;
1638 for (Iterator it=faces.iterator(); it.hasNext(); )
1639 { Face face = (Face)it.next();
1640 if (face.mark == Face.VISIBLE)
1642 dist = face.distanceToPlane (pnt);
1643 if (dist > pointTol)
1646 "Point " + i +
" " + dist +
" above face " +
1647 face.getVertexString());