JavaCAD
QuickHull3DTest.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
36 class QuickHull3DTest
37 {
38 
40  static private final double DOUBLE_PREC = 2.2204460492503131e-16;
41 
43  static boolean triangulate = false;
44 
46  static boolean doTesting = true;
47 
49  static boolean doTiming = false;
50 
52  static boolean debugEnable = false;
53 
55  static final int NO_DEGENERACY = 0;
56 
58  static final int EDGE_DEGENERACY = 1;
59 
61  static final int VERTEX_DEGENERACY = 2;
62 
64  Random rand; // random number generator
65 
67  static boolean testRotation = true;
68 
70  static int degeneracyTest = VERTEX_DEGENERACY;
71 
73  static double epsScale = 2.0;
74 
78  public QuickHull3DTest()
79  {
80  rand = new Random();
81  rand.setSeed (0x1234);
82  }
83 
92  public boolean faceIndicesEqual (int[] indices1, int[] indices2)
93  {
94  if (indices1.length != indices2.length)
95  { return false;
96  }
97  int len = indices1.length;
98  int j;
99  for (j=0; j<len; j++)
100  { if (indices1[0] == indices2[j])
101  { break;
102  }
103  }
104  if (j==len)
105  { return false;
106  }
107  for (int i=1; i<len; i++)
108  { if (indices1[i] != indices2[(j+i)%len])
109  { return false;
110  }
111  }
112  return true;
113  }
114 
123  public double[] randomPoints (int num, double range)
124  {
125  double[] coords = new double[num*3];
126 
127  for (int i=0; i<num; i++)
128  { for (int k=0; k<3; k++)
129  { coords[i*3+k] = 2*range*(rand.nextDouble()-0.5);
130  }
131  }
132  return coords;
133  }
134 
141  private void randomlyPerturb (Point3d pnt, double tol)
142  {
143  pnt.x += tol*(rand.nextDouble()-0.5);
144  pnt.y += tol*(rand.nextDouble()-0.5);
145  pnt.z += tol*(rand.nextDouble()-0.5);
146  }
147 
158  public double[] randomDegeneratePoints (int num, int dimen)
159  {
160  double[] coords= new double[num*3];
161  Point3d pnt = new Point3d();
162 
163  Point3d base = new Point3d();
164  base.setRandom (-1, 1, rand);
165 
166  double tol = DOUBLE_PREC;
167 
168  if (dimen == 0)
169  { for (int i=0; i<num; i++)
170  { pnt.set (base);
171  randomlyPerturb (pnt, tol);
172  coords[i*3+0] = pnt.x;
173  coords[i*3+1] = pnt.y;
174  coords[i*3+2] = pnt.z;
175  }
176  }
177  else if (dimen == 1)
178  { Vector3d u = new Vector3d();
179  u.setRandom (-1, 1, rand);
180  u.normalize();
181  for (int i=0; i<num; i++)
182  { double a = 2*(rand.nextDouble()-0.5);
183  pnt.scale (a, u);
184  pnt.add (base);
185  randomlyPerturb (pnt, tol);
186  coords[i*3+0] = pnt.x;
187  coords[i*3+1] = pnt.y;
188  coords[i*3+2] = pnt.z;
189  }
190  }
191  else // dimen == 2
192  { Vector3d nrm = new Vector3d();
193  nrm.setRandom (-1, 1, rand);
194  nrm.normalize();
195  for (int i=0; i<num; i++)
196  { // compute a random point and project it to the plane
197  Vector3d perp = new Vector3d();
198  pnt.setRandom (-1, 1, rand);
199  perp.scale (pnt.dot(nrm), nrm);
200  pnt.sub (perp);
201  pnt.add (base);
202  randomlyPerturb (pnt, tol);
203  coords[i*3+0] = pnt.x;
204  coords[i*3+1] = pnt.y;
205  coords[i*3+2] = pnt.z;
206  }
207  }
208  return coords;
209  }
210 
219  public double[] randomSphericalPoints (int num, double radius)
220  {
221  double[] coords = new double[num*3];
222  Point3d pnt = new Point3d();
223 
224  for (int i=0; i<num; )
225  { pnt.setRandom (-radius, radius, rand);
226  if (pnt.norm() <= radius)
227  { coords[i*3+0] = pnt.x;
228  coords[i*3+1] = pnt.y;
229  coords[i*3+2] = pnt.z;
230  i++;
231  }
232  }
233  return coords;
234  }
235 
251  public double[] randomCubedPoints (int num, double range, double max)
252  {
253  double[] coords = new double[num*3];
254 
255  for (int i=0; i<num; i++)
256  { for (int k=0; k<3; k++)
257  { double x = 2*range*(rand.nextDouble()-0.5);
258  if (x > max)
259  { x = max;
260  }
261  else if (x < -max)
262  { x = -max;
263  }
264  coords[i*3+k] = x;
265  }
266  }
267  return coords;
268  }
269 
276  private double[] shuffleCoords (double[] coords)
277  {
278  int num = coords.length/3;
279 
280  for (int i=0; i<num; i++)
281  { int i1 = rand.nextInt (num);
282  int i2 = rand.nextInt (num);
283  for (int k=0; k<3; k++)
284  { double tmp = coords[i1*3+k];
285  coords[i1*3+k] = coords[i2*3+k];
286  coords[i2*3+k] = tmp;
287  }
288  }
289  return coords;
290  }
291 
303  public double[] randomGridPoints (int gridSize, double width)
304  {
305  // gridSize gives the number of points across a given dimension
306  // any given coordinate indexed by i has value
307  // (i/(gridSize-1) - 0.5)*width
308 
309  int num = gridSize*gridSize*gridSize;
310 
311  double[] coords = new double[num*3];
312 
313  int idx = 0;
314  for (int i=0; i<gridSize; i++)
315  { for (int j=0; j<gridSize; j++)
316  { for (int k=0; k<gridSize; k++)
317  { coords[idx*3+0] = (i/(double)(gridSize-1) - 0.5)*width;
318  coords[idx*3+1] = (j/(double)(gridSize-1) - 0.5)*width;
319  coords[idx*3+2] = (k/(double)(gridSize-1) - 0.5)*width;
320  idx++;
321  }
322  }
323  }
324  shuffleCoords (coords);
325  return coords;
326  }
327 
335  void explicitFaceCheck (QuickHull3D hull, int[][] checkFaces)
336  throws Exception
337  {
338  int [][] faceIndices = hull.getFaces();
339  if (faceIndices.length != checkFaces.length)
340  { throw new Exception (
341 "Error: " + faceIndices.length + " faces vs. " + checkFaces.length);
342  }
343  // translate face indices back into original indices
344  Point3d[] pnts = hull.getVertices();
345  int[] vtxIndices = hull.getVertexPointIndices();
346 
347  for (int j=0; j<faceIndices.length; j++)
348  { int[] idxs = faceIndices[j];
349  for (int k=0; k<idxs.length; k++)
350  { idxs[k] = vtxIndices[idxs[k]];
351  }
352  }
353  for (int i=0; i<checkFaces.length; i++)
354  { int[] cf = checkFaces[i];
355  int j;
356  for (j=0; j<faceIndices.length; j++)
357  { if (faceIndices[j] != null)
358  { if (faceIndicesEqual (cf, faceIndices[j]))
359  { faceIndices[j] = null;
360  break;
361  }
362  }
363  }
364  if (j == faceIndices.length)
365  { String s = "";
366  for (int k=0; k<cf.length; k++)
367  { s += cf[k] + " ";
368  }
369  throw new Exception ("Error: face " + s + " not found");
370  }
371  }
372  }
373 
375  int cnt = 0;
376 
384  void singleTest (double[] coords, int[][] checkFaces)
385  throws Exception
386  {
387  QuickHull3D hull = new QuickHull3D ();
388  hull.setDebug (debugEnable);
389 
390  hull.build (coords, coords.length/3);
391  if (triangulate)
392  { hull.triangulate();
393  }
394 
395  if (!hull.check(System.out))
396  { (new Throwable()).printStackTrace();
397  System.exit(1);
398  }
399  if (checkFaces != null)
400  { explicitFaceCheck (hull, checkFaces);
401  }
402  if (degeneracyTest != NO_DEGENERACY)
403  { degenerateTest (hull, coords);
404  }
405  }
406 
415  double[] addDegeneracy (
416  int type, double[] coords, QuickHull3D hull)
417  {
418  int numv = coords.length/3;
419  int[][] faces = hull.getFaces();
420  double[] coordsx = new double[coords.length+faces.length*3];
421  for (int i=0; i<coords.length; i++)
422  { coordsx[i] = coords[i];
423  }
424 
425  double[] lam = new double[3];
426  double eps = hull.getDistanceTolerance();
427 
428  for (int i=0; i<faces.length; i++)
429  {
430  // random point on an edge
431  lam[0] = rand.nextDouble();
432  lam[1] = 1-lam[0];
433  lam[2] = 0.0;
434 
435  if (type == VERTEX_DEGENERACY && (i%2 == 0))
436  { lam[0] = 1.0;
437  lam[1] = lam[2] = 0;
438  }
439 
440  for (int j=0; j<3; j++)
441  { int vtxi = faces[i][j];
442  for (int k=0; k<3; k++)
443  { coordsx[numv*3+k] +=
444  lam[j]*coords[vtxi*3+k] +
445  epsScale*eps*(rand.nextDouble()-0.5);
446  }
447  }
448  numv++;
449  }
450  shuffleCoords (coordsx);
451  return coordsx;
452  }
453 
461  void degenerateTest (QuickHull3D hull, double[] coords)
462  throws Exception
463  {
464  double[] coordsx = addDegeneracy (degeneracyTest, coords, hull);
465 
466  QuickHull3D xhull = new QuickHull3D();
467  xhull.setDebug (debugEnable);
468 
469  try
470  { xhull.build (coordsx, coordsx.length/3);
471  if (triangulate)
472  { xhull.triangulate();
473  }
474  }
475  catch (Exception e)
476  { for (int i=0; i<coordsx.length/3; i++)
477  { System.out.println (
478  coordsx[i*3+0]+", "+
479  coordsx[i*3+1]+", "+
480  coordsx[i*3+2]+", ");
481  }
482  }
483 
484  if (!xhull.check(System.out))
485  { (new Throwable()).printStackTrace();
486  System.exit(1);
487  }
488  }
489 
499  void rotateCoords (double[] res, double[] xyz,
500  double roll, double pitch, double yaw)
501  {
502  double sroll = Math.sin (roll);
503  double croll = Math.cos (roll);
504  double spitch = Math.sin (pitch);
505  double cpitch = Math.cos (pitch);
506  double syaw = Math.sin (yaw);
507  double cyaw = Math.cos (yaw);
508 
509  double m00 = croll * cpitch;
510  double m10 = sroll * cpitch;
511  double m20 = - spitch;
512 
513  double m01 = croll * spitch * syaw - sroll * cyaw;
514  double m11 = sroll * spitch * syaw + croll * cyaw;
515  double m21 = cpitch * syaw;
516 
517  double m02 = croll * spitch * cyaw + sroll * syaw;
518  double m12 = sroll * spitch * cyaw - croll * syaw;
519  double m22 = cpitch * cyaw;
520 
521  double x, y, z;
522 
523  for (int i=0; i<xyz.length-2; i+=3)
524  {
525  res[i+0] = m00*xyz[i+0] + m01*xyz[i+1] + m02*xyz[i+2];
526  res[i+1] = m10*xyz[i+0] + m11*xyz[i+1] + m12*xyz[i+2];
527  res[i+2] = m20*xyz[i+0] + m21*xyz[i+1] + m22*xyz[i+2];
528  }
529  }
530 
536  void printCoords (double[] coords)
537  {
538  int nump = coords.length/3;
539  for (int i=0; i<nump; i++)
540  { System.out.println (
541  coords[i*3+0]+", "+
542  coords[i*3+1]+", "+
543  coords[i*3+2]+", ");
544  }
545  }
546 
553  void testException (double[] coords, String msg)
554  {
555  QuickHull3D hull = new QuickHull3D();
556  Exception ex = null;
557  try
558  { hull.build(coords);
559  }
560  catch (Exception e)
561  { ex = e;
562  }
563  if (ex == null)
564  { System.out.println ("Expected exception " + msg);
565  System.out.println ("Got no exception");
566  System.out.println ("Input pnts:");
567  printCoords (coords);
568  System.exit(1);
569  }
570  else if (ex.getMessage() == null ||
571  !ex.getMessage().equals(msg))
572  { System.out.println ("Expected exception " + msg);
573  System.out.println ("Got exception " + ex.getMessage());
574  System.out.println ("Input pnts:");
575  printCoords (coords);
576  System.exit(1);
577  }
578  }
579 
587  void test (double[] coords, int[][] checkFaces)
588  throws Exception
589  {
590  double[][] rpyList = new double[][]
591  {
592  { 0, 0, 0},
593  { 10, 20, 30},
594  { -45, 60, 91},
595  { 125, 67, 81}
596  };
597  double[] xcoords = new double[coords.length];
598 
599  singleTest (coords, checkFaces);
600  if (testRotation)
601  {
602  for (int i=0; i<rpyList.length; i++)
603  { double[] rpy = rpyList[i];
604  rotateCoords (xcoords, coords,
605  Math.toRadians(rpy[0]),
606  Math.toRadians(rpy[1]),
607  Math.toRadians(rpy[2]));
608  singleTest (xcoords, checkFaces);
609  }
610  }
611  }
612 
617  public void explicitAndRandomTests()
618  {
619  try
620  {
621  double[] coords = null;
622 
623  System.out.println (
624 "Testing degenerate input ...");
625  for (int dimen=0; dimen<3; dimen++)
626  { for (int i=0; i<10; i++)
627  { coords = randomDegeneratePoints (10, dimen);
628  if (dimen == 0)
629  { testException (
630  coords, "Input points appear to be coincident");
631  }
632  else if (dimen == 1)
633  { testException (
634  coords, "Input points appear to be colinear");
635  }
636  else if (dimen == 2)
637  { testException (
638  coords, "Input points appear to be coplanar");
639  }
640  }
641  }
642 
643  System.out.println (
644 "Explicit tests ...");
645 
646  // test cases furnished by Mariano Zelke, Berlin
647  coords = new double[]
648  { 21, 0, 0,
649  0, 21, 0,
650  0, 0, 0,
651  18, 2, 6,
652  1, 18, 5,
653  2, 1, 3,
654  14, 3, 10,
655  4, 14, 14,
656  3, 4, 10,
657  10, 6, 12,
658  5, 10, 15,
659  };
660  test (coords, null);
661 
662  coords = new double[]
663  {
664  0.0 , 0.0 , 0.0,
665  21.0, 0.0 , 0.0,
666  0.0 , 21.0, 0.0,
667  2.0 , 1.0 , 2.0,
668  17.0, 2.0 , 3.0,
669  1.0 , 19.0, 6.0,
670  4.0 , 3.0 , 5.0,
671  13.0, 4.0 , 5.0,
672  3.0 , 15.0, 8.0,
673  6.0 , 5.0 , 6.0,
674  9.0 , 6.0 , 11.0,
675  };
676  test (coords, null);
677 
678  System.out.println (
679 "Testing 20 to 200 random points ...");
680  for (int n=20; n<200; n+=10)
681  { // System.out.println (n);
682  for (int i=0; i<10; i++)
683  { coords = randomPoints (n, 1.0);
684  test (coords, null);
685  }
686  }
687 
688  System.out.println (
689 "Testing 20 to 200 random points in a sphere ...");
690  for (int n=20; n<200; n+=10)
691  { // System.out.println (n);
692  for (int i=0; i<10; i++)
693  { coords = randomSphericalPoints (n, 1.0);
694  test (coords, null);
695  }
696  }
697 
698  System.out.println (
699 "Testing 20 to 200 random points clipped to a cube ...");
700  for (int n=20; n<200; n+=10)
701  { // System.out.println (n);
702  for (int i=0; i<10; i++)
703  { coords = randomCubedPoints (n, 1.0, 0.5);
704  test (coords, null);
705  }
706  }
707 
708  System.out.println (
709 "Testing 8 to 1000 randomly shuffled points on a grid ...");
710  for (int n=2; n<=10; n++)
711  { // System.out.println (n*n*n);
712  for (int i=0; i<10; i++)
713  { coords = randomGridPoints (n, 4.0);
714  test (coords, null);
715  }
716  }
717 
718  }
719  catch (Exception e)
720  { e.printStackTrace();
721  System.exit(1);
722  }
723 
724  System.out.println ("\nPassed\n");
725  }
726 
731  public void timingTests()
732  {
733  long t0, t1;
734  int n = 10;
735  QuickHull3D hull = new QuickHull3D ();
736  System.out.println ("warming up ... ");
737  for (int i=0; i<2; i++)
738  { double[] coords = randomSphericalPoints (10000, 1.0);
739  hull.build (coords);
740  }
741  int cnt = 10;
742  for (int i=0; i<4; i++)
743  { n *= 10;
744  double[] coords = randomSphericalPoints (n, 1.0);
745  t0 = System.currentTimeMillis();
746  for (int k=0; k<cnt; k++)
747  { hull.build (coords);
748  }
749  t1 = System.currentTimeMillis();
750  System.out.println (n + " points: " + (t1-t0)/(double)cnt +
751  " msec");
752  }
753  }
754 
766  public static void main (String[] args)
767  {
768  QuickHull3DTest tester = new QuickHull3DTest();
769 
770  for (int i=0; i<args.length; i++)
771  { if (args[i].equals ("-timing"))
772  { doTiming = true;
773  doTesting = false;
774  }
775  else
776  { System.out.println (
777 "Usage: java quickhull3d.QuickHull3DTest [-timing]");
778  System.exit(1);
779  }
780  }
781  if (doTesting)
782  { tester.explicitAndRandomTests();
783  }
784 
785  if (doTiming)
786  { tester.timingTests();
787  }
788  }
789 }