BowlerKernel
QuickHull3D.java
Go to the documentation of this file.
1 
13 package eu.mihosoft.vrl.v3d.ext.quickhull3d;
14 
15 import java.util.*;
16 import java.io.*;
17 
18 // TODO: Auto-generated Javadoc
115 class QuickHull3D
116 {
121  public static final int CLOCKWISE = 0x1;
122 
127  public static final int INDEXED_FROM_ONE = 0x2;
128 
133  public static final int INDEXED_FROM_ZERO = 0x4;
134 
139  public static final int POINT_RELATIVE = 0x8;
140 
145  public static final double AUTOMATIC_TOLERANCE = -1;
146 
148  protected int findIndex = -1;
149 
151  // estimated size of the point set
152  protected double charLength;
153 
155  protected boolean debug = false;
156 
158  protected Vertex[] pointBuffer = new Vertex[0];
159 
161  protected int[] vertexPointIndices = new int[0];
162 
164  private Face[] discardedFaces = new Face[3];
165 
167  private Vertex[] maxVtxs = new Vertex[3];
168 
170  private Vertex[] minVtxs = new Vertex[3];
171 
173  protected Vector faces = new Vector(16);
174 
176  protected Vector horizon = new Vector(16);
177 
179  private FaceList newFaces = new FaceList();
180 
182  private VertexList unclaimed = new VertexList();
183 
185  private VertexList claimed = new VertexList();
186 
188  protected int numVertices;
189 
191  protected int numFaces;
192 
194  protected int numPoints;
195 
197  protected double explicitTolerance = AUTOMATIC_TOLERANCE;
198 
200  protected double tolerance;
201 
208  public boolean getDebug()
209  {
210  return debug;
211  }
212 
218  public void setDebug (boolean enable)
219  {
220  debug = enable;
221  }
222 
226  static private final double DOUBLE_PREC = 2.2204460492503131e-16;
227 
228 
241  public double getDistanceTolerance()
242  {
243  return tolerance;
244  }
245 
255  public void setExplicitDistanceTolerance(double tol)
256  {
257  explicitTolerance = tol;
258  }
259 
266  public double getExplicitDistanceTolerance()
267  {
268  return explicitTolerance;
269  }
270 
277  private void addPointToFace (Vertex vtx, Face face)
278  {
279  vtx.face = face;
280 
281  if (face.outside == null)
282  { claimed.add (vtx);
283  }
284  else
285  { claimed.insertBefore (vtx, face.outside);
286  }
287  face.outside = vtx;
288  }
289 
296  private void removePointFromFace (Vertex vtx, Face face)
297  {
298  if (vtx == face.outside)
299  { if (vtx.next != null && vtx.next.face == face)
300  { face.outside = vtx.next;
301  }
302  else
303  { face.outside = null;
304  }
305  }
306  claimed.delete (vtx);
307  }
308 
315  private Vertex removeAllPointsFromFace (Face face)
316  {
317  if (face.outside != null)
318  {
319  Vertex end = face.outside;
320  while (end.next != null && end.next.face == face)
321  { end = end.next;
322  }
323  claimed.delete (face.outside, end);
324  end.next = null;
325  return face.outside;
326  }
327  else
328  { return null;
329  }
330  }
331 
335  public QuickHull3D ()
336  {
337  }
338 
351  public QuickHull3D (double[] coords)
352  throws IllegalArgumentException
353  {
354  build (coords, coords.length/3);
355  }
356 
366  public QuickHull3D (Point3d[] points)
367  throws IllegalArgumentException
368  {
369  build (points, points.length);
370  }
371 
379  private HalfEdge findHalfEdge (Vertex tail, Vertex head)
380  {
381  // brute force ... OK, since setHull is not used much
382  for (Iterator it=faces.iterator(); it.hasNext(); )
383  { HalfEdge he = ((Face)it.next()).findEdge (tail, head);
384  if (he != null)
385  { return he;
386  }
387  }
388  return null;
389  }
390 
399  protected void setHull (double[] coords, int nump,
400  int[][] faceIndices, int numf)
401  {
402  initBuffers (nump);
403  setPoints (coords, nump);
404  computeMaxAndMin ();
405  for (int i=0; i<numf; i++)
406  { Face face = Face.create (pointBuffer, faceIndices[i]);
407  HalfEdge he = face.he0;
408  do
409  { HalfEdge heOpp = findHalfEdge (he.head(), he.tail());
410  if (heOpp != null)
411  { he.setOpposite (heOpp);
412  }
413  he = he.next;
414  }
415  while (he != face.he0);
416  faces.add (face);
417  }
418  }
419 
426  private void printQhullErrors (Process proc)
427  throws IOException
428  {
429  boolean wrote = false;
430  InputStream es = proc.getErrorStream();
431  while (es.available() > 0)
432  { System.out.write (es.read());
433  wrote = true;
434  }
435  if (wrote)
436  { System.out.println("");
437  }
438  }
439 
447  protected void setFromQhull (double[] coords, int nump,
448  boolean triangulate)
449  {
450  String commandStr = "./qhull i";
451  if (triangulate)
452  { commandStr += " -Qt";
453  }
454  try
455  {
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()));
461 
462  ps.println ("3 " + nump);
463  for (int i=0; i<nump; i++)
464  { ps.println (
465  coords[i*3+0] + " " +
466  coords[i*3+1] + " " +
467  coords[i*3+2]);
468  }
469  ps.flush();
470  ps.close();
471  Vector indexList = new Vector(3);
472  stok.eolIsSignificant(true);
473  printQhullErrors (proc);
474 
475  do
476  { stok.nextToken();
477  }
478  while (stok.sval == null ||
479  !stok.sval.startsWith ("MERGEexact"));
480  for (int i=0; i<4; i++)
481  { stok.nextToken();
482  }
483  if (stok.ttype != StreamTokenizer.TT_NUMBER)
484  { System.out.println ("Expecting number of faces");
485  System.exit(1);
486  }
487  int numf = (int)stok.nval;
488  stok.nextToken(); // clear EOL
489  int[][] faceIndices = new int[numf][];
490  for (int i=0; i<numf; i++)
491  { indexList.clear();
492  while (stok.nextToken() != StreamTokenizer.TT_EOL)
493  { if (stok.ttype != StreamTokenizer.TT_NUMBER)
494  { System.out.println ("Expecting face index");
495  System.exit(1);
496  }
497  indexList.add (0, new Integer((int)stok.nval));
498  }
499  faceIndices[i] = new int[indexList.size()];
500  int k = 0;
501  for (Iterator it=indexList.iterator(); it.hasNext(); )
502  { faceIndices[i][k++] = ((Integer)it.next()).intValue();
503  }
504  }
505  setHull (coords, nump, faceIndices, numf);
506  }
507  catch (Exception e)
508  { e.printStackTrace();
509  System.exit(1);
510  }
511  }
512 
518  private void printPoints (PrintStream ps)
519  {
520  for (int i=0; i<numPoints; i++)
521  { Point3d pnt = pointBuffer[i].pnt;
522  ps.println (pnt.x + ", " + pnt.y + ", " + pnt.z + ",");
523  }
524  }
525 
537  public void build (double[] coords)
538  throws IllegalArgumentException
539  {
540  build (coords, coords.length/3);
541  }
542 
556  public void build (double[] coords, int nump)
557  throws IllegalArgumentException
558  {
559  if (nump < 4)
560  { throw new IllegalArgumentException (
561  "Less than four input points specified");
562  }
563  if (coords.length/3 < nump)
564  { throw new IllegalArgumentException (
565  "Coordinate array too small for specified number of points");
566  }
567  initBuffers (nump);
568  setPoints (coords, nump);
569  buildHull ();
570  }
571 
580  public void build (Point3d[] points)
581  throws IllegalArgumentException
582  {
583  build (points, points.length);
584  }
585 
595  public void build (Point3d[] points, int nump)
596  throws IllegalArgumentException
597  {
598  if (nump < 4)
599  { throw new IllegalArgumentException (
600  "Less than four input points specified");
601  }
602  if (points.length < nump)
603  { throw new IllegalArgumentException (
604  "Point array too small for specified number of points");
605  }
606  initBuffers (nump);
607  setPoints (points, nump);
608  buildHull ();
609  }
610 
617  public void triangulate ()
618  {
619  double minArea = 1000*charLength*DOUBLE_PREC;
620  newFaces.clear();
621  for (Iterator it=faces.iterator(); it.hasNext(); )
622  { Face face = (Face)it.next();
623  if (face.mark == Face.VISIBLE)
624  {
625  face.triangulate (newFaces, minArea);
626  // splitFace (face);
627  }
628  }
629  for (Face face=newFaces.first(); face!=null; face=face.next)
630  { faces.add (face);
631  }
632  }
633 
634 // private void splitFace (Face face)
635 // {
636 // Face newFace = face.split();
637 // if (newFace != null)
638 // { newFaces.add (newFace);
639 // splitFace (newFace);
640 // splitFace (face);
641 // }
642 // }
643 
649 protected void initBuffers (int nump)
650  {
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];
656  }
657  for (int i=pointBuffer.length; i<nump; i++)
658  { newBuffer[i] = new Vertex();
659  }
660  pointBuffer = newBuffer;
661  }
662  faces.clear();
663  claimed.clear();
664  numFaces = 0;
665  numPoints = nump;
666  }
667 
674  protected void setPoints (double[] coords, int nump)
675  {
676  for (int i=0; i<nump; i++)
677  {
678  Vertex vtx = pointBuffer[i];
679  vtx.pnt.set (coords[i*3+0], coords[i*3+1], coords[i*3+2]);
680  vtx.index = i;
681  }
682  }
683 
690  protected void setPoints (Point3d[] pnts, int nump)
691  {
692  for (int i=0; i<nump; i++)
693  {
694  Vertex vtx = pointBuffer[i];
695  vtx.pnt.set (pnts[i]);
696  vtx.index = i;
697  }
698  }
699 
703  protected void computeMaxAndMin ()
704  {
705  Vector3d max = new Vector3d();
706  Vector3d min = new Vector3d();
707 
708  for (int i=0; i<3; i++)
709  { maxVtxs[i] = minVtxs[i] = pointBuffer[0];
710  }
711  max.set (pointBuffer[0].pnt);
712  min.set (pointBuffer[0].pnt);
713 
714  for (int i=1; i<numPoints; i++)
715  { Point3d pnt = pointBuffer[i].pnt;
716  if (pnt.x > max.x)
717  { max.x = pnt.x;
718  maxVtxs[0] = pointBuffer[i];
719  }
720  else if (pnt.x < min.x)
721  { min.x = pnt.x;
722  minVtxs[0] = pointBuffer[i];
723  }
724  if (pnt.y > max.y)
725  { max.y = pnt.y;
726  maxVtxs[1] = pointBuffer[i];
727  }
728  else if (pnt.y < min.y)
729  { min.y = pnt.y;
730  minVtxs[1] = pointBuffer[i];
731  }
732  if (pnt.z > max.z)
733  { max.z = pnt.z;
734  maxVtxs[2] = pointBuffer[i];
735  }
736  else if (pnt.z < min.z)
737  { min.z = pnt.z;
738  minVtxs[2] = pointBuffer[i];
739  }
740  }
741 
742  // this epsilon formula comes from QuickHull, and I'm
743  // not about to quibble.
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)
747  { 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)));
751  }
752  else
753  { tolerance = explicitTolerance;
754  }
755  }
756 
762  protected void createInitialSimplex ()
763  throws IllegalArgumentException
764  {
765  double max = 0;
766  int imax = 0;
767 
768  for (int i=0; i<3; i++)
769  { double diff = maxVtxs[i].pnt.get(i)-minVtxs[i].pnt.get(i);
770  if (diff > max)
771  { max = diff;
772  imax = i;
773  }
774  }
775 
776  if (max <= tolerance)
777  { throw new IllegalArgumentException (
778 "Input points appear to be coincident");
779  }
780  Vertex[] vtx = new Vertex[4];
781  // set first two vertices to be those with the greatest
782  // one dimensional separation
783 
784  vtx[0] = maxVtxs[imax];
785  vtx[1] = minVtxs[imax];
786 
787  // set third vertex to be the vertex farthest from
788  // the line between vtx0 and vtx1
789  Vector3d u01 = new Vector3d();
790  Vector3d diff02 = new Vector3d();
791  Vector3d nrml = new Vector3d();
792  Vector3d xprod = new Vector3d();
793  double maxSqr = 0;
794  u01.sub (vtx[1].pnt, vtx[0].pnt);
795  u01.normalize();
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] && // paranoid
802  pointBuffer[i] != vtx[1])
803  { maxSqr = lenSqr;
804  vtx[2] = pointBuffer[i];
805  nrml.set (xprod);
806  }
807  }
808  if (Math.sqrt(maxSqr) <= 100*tolerance)
809  { throw new IllegalArgumentException (
810 "Input points appear to be colinear");
811  }
812  nrml.normalize();
813 
814 
815  double maxDist = 0;
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] && // paranoid
821  pointBuffer[i] != vtx[1] &&
822  pointBuffer[i] != vtx[2])
823  { maxDist = dist;
824  vtx[3] = pointBuffer[i];
825  }
826  }
827  if (Math.abs(maxDist) <= 100*tolerance)
828  { throw new IllegalArgumentException (
829 "Input points appear to be coplanar");
830  }
831 
832  if (debug)
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);
838  }
839 
840  Face[] tris = new Face[4];
841 
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]);
847 
848  for (int i=0; i<3; i++)
849  { int k = (i+1)%3;
850  tris[i+1].getEdge(1).setOpposite (tris[k+1].getEdge(0));
851  tris[i+1].getEdge(2).setOpposite (tris[0].getEdge(k));
852  }
853  }
854  else
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]);
859 
860  for (int i=0; i<3; i++)
861  { int k = (i+1)%3;
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));
864  }
865  }
866 
867 
868  for (int i=0; i<4; i++)
869  { faces.add (tris[i]);
870  }
871 
872  for (int i=0; i<numPoints; i++)
873  { Vertex v = pointBuffer[i];
874 
875  if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3])
876  { continue;
877  }
878 
879  maxDist = tolerance;
880  Face maxFace = null;
881  for (int k=0; k<4; k++)
882  { double dist = tris[k].distanceToPlane (v.pnt);
883  if (dist > maxDist)
884  { maxFace = tris[k];
885  maxDist = dist;
886  }
887  }
888  if (maxFace != null)
889  { addPointToFace (v, maxFace);
890  }
891  }
892  }
893 
899  public int getNumVertices()
900  {
901  return numVertices;
902  }
903 
911  public Point3d[] getVertices()
912  {
913  Point3d[] vtxs = new Point3d[numVertices];
914  for (int i=0; i<numVertices; i++)
915  { vtxs[i] = pointBuffer[vertexPointIndices[i]].pnt;
916  }
917  return vtxs;
918  }
919 
930  public int getVertices(double[] coords)
931  {
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;
937  }
938  return numVertices;
939  }
940 
947  public int[] getVertexPointIndices()
948  {
949  int[] indices = new int[numVertices];
950  for (int i=0; i<numVertices; i++)
951  { indices[i] = vertexPointIndices[i];
952  }
953  return indices;
954  }
955 
961  public int getNumFaces()
962  {
963  return faces.size();
964  }
965 
982  public int[][] getFaces ()
983  {
984  return getFaces(0);
985  }
986 
1004  public int[][] getFaces (int indexFlags)
1005  {
1006  int[][] allFaces = new int[faces.size()][];
1007  int k = 0;
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);
1012  k++;
1013  }
1014  return allFaces;
1015  }
1016 
1038  public void print (PrintStream ps)
1039  {
1040  print (ps, 0);
1041  }
1042 
1064  public void print (PrintStream ps, int indexFlags)
1065  {
1066  if ((indexFlags & INDEXED_FROM_ZERO) == 0)
1067  { indexFlags |= INDEXED_FROM_ONE;
1068  }
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);
1072  }
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);
1077 
1078  ps.print ("f");
1079  for (int k=0; k<indices.length; k++)
1080  { ps.print (" " + indices[k]);
1081  }
1082  ps.println ("");
1083  }
1084  }
1085 
1093  private void getFaceIndices (int[] indices, Face face, int flags)
1094  {
1095  boolean ccw = ((flags & CLOCKWISE) == 0);
1096  boolean indexedFromOne = ((flags & INDEXED_FROM_ONE) != 0);
1097  boolean pointRelative = ((flags & POINT_RELATIVE) != 0);
1098 
1099  HalfEdge hedge = face.he0;
1100  int k = 0;
1101  do
1102  { int idx = hedge.head().index;
1103  if (pointRelative)
1104  { idx = vertexPointIndices[idx];
1105  }
1106  if (indexedFromOne)
1107  { idx++;
1108  }
1109  indices[k++] = idx;
1110  hedge = (ccw ? hedge.next : hedge.prev);
1111  }
1112  while (hedge != face.he0);
1113  }
1114 
1120  protected void resolveUnclaimedPoints (FaceList newFaces)
1121  {
1122  Vertex vtxNext = unclaimed.first();
1123  for (Vertex vtx=vtxNext; vtx!=null; vtx=vtxNext)
1124  { vtxNext = vtx.next;
1125 
1126  double maxDist = tolerance;
1127  Face maxFace = null;
1128  for (Face newFace=newFaces.first(); newFace != null;
1129  newFace=newFace.next)
1130  {
1131  if (newFace.mark == Face.VISIBLE)
1132  { double dist = newFace.distanceToPlane(vtx.pnt);
1133  if (dist > maxDist)
1134  { maxDist = dist;
1135  maxFace = newFace;
1136  }
1137  if (maxDist > 1000*tolerance)
1138  { break;
1139  }
1140  }
1141  }
1142  if (maxFace != null)
1143  {
1144  addPointToFace (vtx, maxFace);
1145  if (debug && vtx.index == findIndex)
1146  { System.out.println (findIndex + " CLAIMED BY " +
1147  maxFace.getVertexString());
1148  }
1149  }
1150  else
1151  { if (debug && vtx.index == findIndex)
1152  { System.out.println (findIndex + " DISCARDED");
1153  }
1154  }
1155  }
1156  }
1157 
1164  protected void deleteFacePoints (Face face, Face absorbingFace)
1165  {
1166  Vertex faceVtxs = removeAllPointsFromFace (face);
1167  if (faceVtxs != null)
1168  {
1169  if (absorbingFace == null)
1170  { unclaimed.addAll (faceVtxs);
1171  }
1172  else
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)
1178  {
1179  addPointToFace (vtx, absorbingFace);
1180  }
1181  else
1182  {
1183  unclaimed.add (vtx);
1184  }
1185  }
1186  }
1187  }
1188  }
1189 
1191  private static final int NONCONVEX_WRT_LARGER_FACE = 1;
1192 
1194  private static final int NONCONVEX = 2;
1195 
1202  protected double oppFaceDistance (HalfEdge he)
1203  {
1204  return he.face.distanceToPlane (he.opposite.face.getCentroid());
1205  }
1206 
1214  private boolean doAdjacentMerge (Face face, int mergeType)
1215  {
1216  HalfEdge hedge = face.he0;
1217 
1218  boolean convex = true;
1219  do
1220  { Face oppFace = hedge.oppositeFace();
1221  boolean merge = false;
1222  double dist1, dist2;
1223 
1224  if (mergeType == NONCONVEX)
1225  { // then merge faces if they are definitively non-convex
1226  if (oppFaceDistance (hedge) > -tolerance ||
1227  oppFaceDistance (hedge.opposite) > -tolerance)
1228  { merge = true;
1229  }
1230  }
1231  else // mergeType == NONCONVEX_WRT_LARGER_FACE
1232  { // merge faces if they are parallel or non-convex
1233  // wrt to the larger face; otherwise, just mark
1234  // the face non-convex for the second pass.
1235  if (face.area > oppFace.area)
1236  { if ((dist1 = oppFaceDistance (hedge)) > -tolerance)
1237  { merge = true;
1238  }
1239  else if (oppFaceDistance (hedge.opposite) > -tolerance)
1240  { convex = false;
1241  }
1242  }
1243  else
1244  { if (oppFaceDistance (hedge.opposite) > -tolerance)
1245  { merge = true;
1246  }
1247  else if (oppFaceDistance (hedge) > -tolerance)
1248  { convex = false;
1249  }
1250  }
1251  }
1252 
1253  if (merge)
1254  { if (debug)
1255  { System.out.println (
1256  " merging " + face.getVertexString() + " and " +
1257  oppFace.getVertexString());
1258  }
1259 
1260  int numd = face.mergeAdjacentFace (hedge, discardedFaces);
1261  for (int i=0; i<numd; i++)
1262  { deleteFacePoints (discardedFaces[i], face);
1263  }
1264  if (debug)
1265  { System.out.println (
1266  " result: " + face.getVertexString());
1267  }
1268  return true;
1269  }
1270  hedge = hedge.next;
1271  }
1272  while (hedge != face.he0);
1273  if (!convex)
1274  { face.mark = Face.NON_CONVEX;
1275  }
1276  return false;
1277  }
1278 
1287  protected void calculateHorizon (
1288  Point3d eyePnt, HalfEdge edge0, Face face, Vector horizon)
1289  {
1290 // oldFaces.add (face);
1291  deleteFacePoints (face, null);
1292  face.mark = Face.DELETED;
1293  if (debug)
1294  { System.out.println (" visiting face " + face.getVertexString());
1295  }
1296  HalfEdge edge;
1297  if (edge0 == null)
1298  { edge0 = face.getEdge(0);
1299  edge = edge0;
1300  }
1301  else
1302  { edge = edge0.getNext();
1303  }
1304  do
1305  { Face oppFace = edge.oppositeFace();
1306  if (oppFace.mark == Face.VISIBLE)
1307  { if (oppFace.distanceToPlane (eyePnt) > tolerance)
1308  { calculateHorizon (eyePnt, edge.getOpposite(),
1309  oppFace, horizon);
1310  }
1311  else
1312  { horizon.add (edge);
1313  if (debug)
1314  { System.out.println (" adding horizon edge " +
1315  edge.getVertexString());
1316  }
1317  }
1318  }
1319  edge = edge.getNext();
1320  }
1321  while (edge != edge0);
1322  }
1323 
1331  private HalfEdge addAdjoiningFace (
1332  Vertex eyeVtx, HalfEdge he)
1333  {
1334  Face face = Face.createTriangle (
1335  eyeVtx, he.tail(), he.head());
1336  faces.add (face);
1337  face.getEdge(-1).setOpposite(he.getOpposite());
1338  return face.getEdge(0);
1339  }
1340 
1348  protected void addNewFaces (
1349  FaceList newFaces, Vertex eyeVtx, Vector horizon)
1350  {
1351  newFaces.clear();
1352 
1353  HalfEdge hedgeSidePrev = null;
1354  HalfEdge hedgeSideBegin = null;
1355 
1356  for (Iterator it=horizon.iterator(); it.hasNext(); )
1357  { HalfEdge horizonHe = (HalfEdge)it.next();
1358  HalfEdge hedgeSide = addAdjoiningFace (eyeVtx, horizonHe);
1359  if (debug)
1360  { System.out.println (
1361  "new face: " + hedgeSide.face.getVertexString());
1362  }
1363  if (hedgeSidePrev != null)
1364  { hedgeSide.next.setOpposite (hedgeSidePrev);
1365  }
1366  else
1367  { hedgeSideBegin = hedgeSide;
1368  }
1369  newFaces.add (hedgeSide.getFace());
1370  hedgeSidePrev = hedgeSide;
1371  }
1372  hedgeSideBegin.next.setOpposite (hedgeSidePrev);
1373  }
1374 
1380  protected Vertex nextPointToAdd()
1381  {
1382  if (!claimed.isEmpty())
1383  { Face eyeFace = claimed.first().face;
1384  Vertex eyeVtx = null;
1385  double maxDist = 0;
1386  for (Vertex vtx=eyeFace.outside;
1387  vtx != null && vtx.face==eyeFace;
1388  vtx = vtx.next)
1389  { double dist = eyeFace.distanceToPlane(vtx.pnt);
1390  if (dist > maxDist)
1391  { maxDist = dist;
1392  eyeVtx = vtx;
1393  }
1394  }
1395  return eyeVtx;
1396  }
1397  else
1398  { return null;
1399  }
1400  }
1401 
1407  protected void addPointToHull(Vertex eyeVtx)
1408  {
1409  horizon.clear();
1410  unclaimed.clear();
1411 
1412  if (debug)
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());
1417  }
1418  removePointFromFace (eyeVtx, eyeVtx.face);
1419  calculateHorizon (eyeVtx.pnt, null, eyeVtx.face, horizon);
1420  newFaces.clear();
1421  addNewFaces (newFaces, eyeVtx, horizon);
1422 
1423  // first merge pass ... merge faces which are non-convex
1424  // as determined by the larger face
1425 
1426  for (Face face = newFaces.first(); face!=null; face=face.next)
1427  {
1428  if (face.mark == Face.VISIBLE)
1429  { while (doAdjacentMerge(face, NONCONVEX_WRT_LARGER_FACE))
1430  ;
1431  }
1432  }
1433  // second merge pass ... merge faces which are non-convex
1434  // wrt either face
1435  for (Face face = newFaces.first(); face!=null; face=face.next)
1436  {
1437  if (face.mark == Face.NON_CONVEX)
1438  { face.mark = Face.VISIBLE;
1439  while (doAdjacentMerge(face, NONCONVEX))
1440  ;
1441  }
1442  }
1443  resolveUnclaimedPoints(newFaces);
1444  }
1445 
1449  protected void buildHull ()
1450  {
1451  int cnt = 0;
1452  Vertex eyeVtx;
1453 
1454  computeMaxAndMin ();
1455  createInitialSimplex ();
1456  while ((eyeVtx = nextPointToAdd()) != null)
1457  { addPointToHull (eyeVtx);
1458  cnt++;
1459  if (debug)
1460  { System.out.println ("iteration " + cnt + " done");
1461  }
1462  }
1463  reindexFacesAndVertices();
1464  if (debug)
1465  { System.out.println ("hull done");
1466  }
1467  }
1468 
1475  private void markFaceVertices (Face face, int mark)
1476  {
1477  HalfEdge he0 = face.getFirstEdge();
1478  HalfEdge he = he0;
1479  do
1480  { he.head().index = mark;
1481  he = he.next;
1482  }
1483  while (he != he0);
1484  }
1485 
1489  protected void reindexFacesAndVertices()
1490  {
1491  for (int i=0; i<numPoints; i++)
1492  { pointBuffer[i].index = -1;
1493  }
1494  // remove inactive faces and mark active vertices
1495  numFaces = 0;
1496  for (Iterator it=faces.iterator(); it.hasNext(); )
1497  { Face face = (Face)it.next();
1498  if (face.mark != Face.VISIBLE)
1499  { it.remove();
1500  }
1501  else
1502  { markFaceVertices (face, 0);
1503  numFaces++;
1504  }
1505  }
1506  // reindex vertices
1507  numVertices = 0;
1508  for (int i=0; i<numPoints; i++)
1509  { Vertex vtx = pointBuffer[i];
1510  if (vtx.index == 0)
1511  { vertexPointIndices[numVertices] = i;
1512  vtx.index = numVertices++;
1513  }
1514  }
1515  }
1516 
1525  protected boolean checkFaceConvexity (
1526  Face face, double tol, PrintStream ps)
1527  {
1528  double dist;
1529  HalfEdge he = face.he0;
1530  do
1531  { face.checkConsistency();
1532  // make sure edge is convex
1533  dist = oppFaceDistance (he);
1534  if (dist > tol)
1535  { if (ps != null)
1536  { ps.println ("Edge " + he.getVertexString() +
1537  " non-convex by " + dist);
1538  }
1539  return false;
1540  }
1541  dist = oppFaceDistance (he.opposite);
1542  if (dist > tol)
1543  { if (ps != null)
1544  { ps.println ("Opposite edge " +
1545  he.opposite.getVertexString() +
1546  " non-convex by " + dist);
1547  }
1548  return false;
1549  }
1550  if (he.next.oppositeFace() == he.oppositeFace())
1551  { if (ps != null)
1552  { ps.println ("Redundant vertex " + he.head().index +
1553  " in face " + face.getVertexString());
1554  }
1555  return false;
1556  }
1557  he = he.next;
1558  }
1559  while (he != face.he0);
1560  return true;
1561  }
1562 
1570  protected boolean checkFaces(double tol, PrintStream ps)
1571  {
1572  // check edge convexity
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))
1578  { convex = false;
1579  }
1580  }
1581  }
1582  return convex;
1583  }
1584 
1597  public boolean check (PrintStream ps)
1598  {
1599  return check (ps, getDistanceTolerance());
1600  }
1601 
1622  public boolean check (PrintStream ps, double tol)
1623 
1624  {
1625  // check to make sure all edges are fully connected
1626  // and that the edges are convex
1627  double dist;
1628  double pointTol = 10*tol;
1629 
1630  if (!checkFaces(tolerance, ps))
1631  { return false;
1632  }
1633 
1634  // check point inclusion
1635 
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)
1641  {
1642  dist = face.distanceToPlane (pnt);
1643  if (dist > pointTol)
1644  { if (ps != null)
1645  { ps.println (
1646  "Point " + i + " " + dist + " above face " +
1647  face.getVertexString());
1648  }
1649  return false;
1650  }
1651  }
1652  }
1653  }
1654  return true;
1655  }
1656 }