13 package eu.mihosoft.vrl.v3d.ext.quickhull3d;
40 static private final double DOUBLE_PREC = 2.2204460492503131e-16;
43 static boolean triangulate =
false;
46 static boolean doTesting =
true;
49 static boolean doTiming =
false;
52 static boolean debugEnable =
false;
55 static final int NO_DEGENERACY = 0;
58 static final int EDGE_DEGENERACY = 1;
61 static final int VERTEX_DEGENERACY = 2;
67 static boolean testRotation =
true;
70 static int degeneracyTest = VERTEX_DEGENERACY;
73 static double epsScale = 2.0;
78 public QuickHull3DTest()
81 rand.setSeed (0x1234);
92 public boolean faceIndicesEqual (
int[] indices1,
int[] indices2)
94 if (indices1.length != indices2.length)
97 int len = indices1.length;
100 {
if (indices1[0] == indices2[j])
107 for (
int i=1; i<len; i++)
108 {
if (indices1[i] != indices2[(j+i)%len])
123 public double[] randomPoints (
int num,
double range)
125 double[] coords =
new double[num*3];
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);
141 private void randomlyPerturb (Point3d pnt,
double tol)
143 pnt.x += tol*(rand.nextDouble()-0.5);
144 pnt.y += tol*(rand.nextDouble()-0.5);
145 pnt.z += tol*(rand.nextDouble()-0.5);
158 public double[] randomDegeneratePoints (
int num,
int dimen)
160 double[] coords=
new double[num*3];
161 Point3d pnt =
new Point3d();
163 Point3d base =
new Point3d();
164 base.setRandom (-1, 1, rand);
166 double tol = DOUBLE_PREC;
169 {
for (
int i=0; i<num; i++)
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;
178 { Vector3d u =
new Vector3d();
179 u.setRandom (-1, 1, rand);
181 for (
int i=0; i<num; i++)
182 {
double a = 2*(rand.nextDouble()-0.5);
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;
192 { Vector3d nrm =
new Vector3d();
193 nrm.setRandom (-1, 1, rand);
195 for (
int i=0; i<num; i++)
197 Vector3d perp =
new Vector3d();
198 pnt.setRandom (-1, 1, rand);
199 perp.scale (pnt.dot(nrm), nrm);
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;
219 public double[] randomSphericalPoints (
int num,
double radius)
221 double[] coords =
new double[num*3];
222 Point3d pnt =
new Point3d();
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;
251 public double[] randomCubedPoints (
int num,
double range,
double max)
253 double[] coords =
new double[num*3];
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);
276 private double[] shuffleCoords (
double[] coords)
278 int num = coords.length/3;
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;
303 public double[] randomGridPoints (
int gridSize,
double width)
309 int num = gridSize*gridSize*gridSize;
311 double[] coords =
new double[num*3];
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;
324 shuffleCoords (coords);
335 void explicitFaceCheck (QuickHull3D hull,
int[][] checkFaces)
338 int [][] faceIndices = hull.getFaces();
339 if (faceIndices.length != checkFaces.length)
340 {
throw new Exception (
341 "Error: " + faceIndices.length +
" faces vs. " + checkFaces.length);
344 Point3d[] pnts = hull.getVertices();
345 int[] vtxIndices = hull.getVertexPointIndices();
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]];
353 for (
int i=0; i<checkFaces.length; i++)
354 {
int[] cf = checkFaces[i];
356 for (j=0; j<faceIndices.length; j++)
357 {
if (faceIndices[j] !=
null)
358 {
if (faceIndicesEqual (cf, faceIndices[j]))
359 { faceIndices[j] =
null;
364 if (j == faceIndices.length)
366 for (
int k=0; k<cf.length; k++)
369 throw new Exception (
"Error: face " + s +
" not found");
384 void singleTest (
double[] coords,
int[][] checkFaces)
387 QuickHull3D hull =
new QuickHull3D ();
388 hull.setDebug (debugEnable);
390 hull.build (coords, coords.length/3);
392 { hull.triangulate();
395 if (!hull.check(System.out))
396 { (
new Throwable()).printStackTrace();
399 if (checkFaces !=
null)
400 { explicitFaceCheck (hull, checkFaces);
402 if (degeneracyTest != NO_DEGENERACY)
403 { degenerateTest (hull, coords);
415 double[] addDegeneracy (
416 int type,
double[] coords, QuickHull3D hull)
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];
425 double[] lam =
new double[3];
426 double eps = hull.getDistanceTolerance();
428 for (
int i=0; i<faces.length; i++)
431 lam[0] = rand.nextDouble();
435 if (type == VERTEX_DEGENERACY && (i%2 == 0))
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);
450 shuffleCoords (coordsx);
461 void degenerateTest (QuickHull3D hull,
double[] coords)
464 double[] coordsx = addDegeneracy (degeneracyTest, coords, hull);
466 QuickHull3D xhull =
new QuickHull3D();
467 xhull.setDebug (debugEnable);
470 { xhull.build (coordsx, coordsx.length/3);
472 { xhull.triangulate();
476 {
for (
int i=0; i<coordsx.length/3; i++)
477 { System.out.println (
480 coordsx[i*3+2]+
", ");
484 if (!xhull.check(System.out))
485 { (
new Throwable()).printStackTrace();
499 void rotateCoords (
double[] res,
double[] xyz,
500 double roll,
double pitch,
double yaw)
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);
509 double m00 = croll * cpitch;
510 double m10 = sroll * cpitch;
511 double m20 = - spitch;
513 double m01 = croll * spitch * syaw - sroll * cyaw;
514 double m11 = sroll * spitch * syaw + croll * cyaw;
515 double m21 = cpitch * syaw;
517 double m02 = croll * spitch * cyaw + sroll * syaw;
518 double m12 = sroll * spitch * cyaw - croll * syaw;
519 double m22 = cpitch * cyaw;
523 for (
int i=0; i<xyz.length-2; i+=3)
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];
536 void printCoords (
double[] coords)
538 int nump = coords.length/3;
539 for (
int i=0; i<nump; i++)
540 { System.out.println (
553 void testException (
double[] coords, String msg)
555 QuickHull3D hull =
new QuickHull3D();
558 { hull.build(coords);
564 { System.out.println (
"Expected exception " + msg);
565 System.out.println (
"Got no exception");
566 System.out.println (
"Input pnts:");
567 printCoords (coords);
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);
587 void test (
double[] coords,
int[][] checkFaces)
590 double[][] rpyList =
new double[][]
597 double[] xcoords =
new double[coords.length];
599 singleTest (coords, checkFaces);
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);
617 public void explicitAndRandomTests()
621 double[] coords =
null;
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);
630 coords,
"Input points appear to be coincident");
634 coords,
"Input points appear to be colinear");
638 coords,
"Input points appear to be coplanar");
644 "Explicit tests ...");
647 coords =
new double[]
662 coords =
new double[]
679 "Testing 20 to 200 random points ...");
680 for (
int n=20; n<200; n+=10)
682 for (
int i=0; i<10; i++)
683 { coords = randomPoints (n, 1.0);
689 "Testing 20 to 200 random points in a sphere ...");
690 for (
int n=20; n<200; n+=10)
692 for (
int i=0; i<10; i++)
693 { coords = randomSphericalPoints (n, 1.0);
699 "Testing 20 to 200 random points clipped to a cube ...");
700 for (
int n=20; n<200; n+=10)
702 for (
int i=0; i<10; i++)
703 { coords = randomCubedPoints (n, 1.0, 0.5);
709 "Testing 8 to 1000 randomly shuffled points on a grid ...");
710 for (
int n=2; n<=10; n++)
712 for (
int i=0; i<10; i++)
713 { coords = randomGridPoints (n, 4.0);
720 { e.printStackTrace();
724 System.out.println (
"\nPassed\n");
731 public void timingTests()
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);
742 for (
int i=0; i<4; i++)
744 double[] coords = randomSphericalPoints (n, 1.0);
745 t0 = System.currentTimeMillis();
746 for (
int k=0; k<cnt; k++)
747 { hull.build (coords);
749 t1 = System.currentTimeMillis();
750 System.out.println (n +
" points: " + (t1-t0)/(
double)cnt +
766 public static void main (String[] args)
768 QuickHull3DTest tester =
new QuickHull3DTest();
770 for (
int i=0; i<args.length; i++)
771 {
if (args[i].equals (
"-timing"))
776 { System.out.println (
777 "Usage: java quickhull3d.QuickHull3DTest [-timing]");
782 { tester.explicitAndRandomTests();
786 { tester.timingTests();