1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.numerics.quickhull;
39
40 import java.io.PrintStream;
41 import java.util.Arrays;
42 import java.util.Iterator;
43 import java.util.Vector;
44 import java.util.logging.Level;
45 import java.util.logging.Logger;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148 public class QuickHull3D {
149
150
151
152
153 private static final Logger logger = Logger.getLogger(QuickHull3D.class.getName());
154
155
156
157
158
159 public static final int CLOCKWISE = 0x1;
160
161
162
163
164
165 public static final int INDEXED_FROM_ONE = 0x2;
166
167
168
169
170
171 public static final int INDEXED_FROM_ZERO = 0x4;
172
173
174
175
176
177 public static final int POINT_RELATIVE = 0x8;
178
179
180
181
182
183 public static final double AUTOMATIC_TOLERANCE = -1;
184
185 protected int findIndex = -1;
186
187
188 protected double charLength;
189
190 protected Vertex[] pointBuffer = new Vertex[0];
191
192 protected int[] vertexPointIndices = new int[0];
193
194 private Face[] discardedFaces = new Face[3];
195
196 private Vertex[] maxVtxs = new Vertex[3];
197
198 private Vertex[] minVtxs = new Vertex[3];
199
200 protected Vector<Face> faces = new Vector<>(16);
201
202 protected Vector<HalfEdge> horizon = new Vector<>(16);
203
204 private FaceList newFaces = new FaceList();
205
206 private VertexList unclaimed = new VertexList();
207
208 private VertexList claimed = new VertexList();
209
210 protected int numVertices;
211
212 protected int numFaces;
213
214 protected int numPoints;
215
216 protected double explicitTolerance = AUTOMATIC_TOLERANCE;
217
218 protected double tolerance;
219
220
221
222
223 private static final double DOUBLE_PREC = 2.2204460492503131e-16;
224
225
226
227
228
229
230
231
232
233
234
235
236
237 public double getDistanceTolerance() {
238 return tolerance;
239 }
240
241
242
243
244
245
246
247
248
249
250 public void setExplicitDistanceTolerance(double tol) {
251 explicitTolerance = tol;
252 }
253
254
255
256
257
258
259
260 public double getExplicitDistanceTolerance() {
261 return explicitTolerance;
262 }
263
264 private void addPointToFace(Vertex vtx, Face face) {
265 vtx.face = face;
266
267 if (face.outside == null) {
268 claimed.add(vtx);
269 } else {
270 claimed.insertBefore(vtx, face.outside);
271 }
272 face.outside = vtx;
273 }
274
275 private void removePointFromFace(Vertex vtx, Face face) {
276 if (vtx == face.outside) {
277 if (vtx.next != null && vtx.next.face == face) {
278 face.outside = vtx.next;
279 } else {
280 face.outside = null;
281 }
282 }
283 claimed.delete(vtx);
284 }
285
286 private Vertex removeAllPointsFromFace(Face face) {
287 if (face.outside != null) {
288 Vertex end = face.outside;
289 while (end.next != null && end.next.face == face) {
290 end = end.next;
291 }
292 claimed.delete(face.outside, end);
293 end.next = null;
294 return face.outside;
295 } else {
296 return null;
297 }
298 }
299
300
301
302
303 public QuickHull3D() {
304 }
305
306
307
308
309
310
311
312
313
314
315 public QuickHull3D(double[] coords) throws IllegalArgumentException {
316 build(coords, coords.length / 3);
317 }
318
319
320
321
322
323
324
325
326
327 public QuickHull3D(Point3d[] points) throws IllegalArgumentException {
328 build(points, points.length);
329 }
330
331
332
333
334
335
336
337
338
339 private HalfEdge findHalfEdge(Vertex tail, Vertex head) {
340
341 for (Face face : faces) {
342 HalfEdge he = face.findEdge(tail, head);
343 if (he != null) {
344 return he;
345 }
346 }
347 return null;
348 }
349
350
351
352
353
354
355
356
357
358
359 protected void setHull(double[] coords, int nump, int[][] faceIndices, int numf) {
360 initBuffers(nump);
361 setPoints(coords, nump);
362 computeMaxAndMin();
363 for (int i = 0; i < numf; i++) {
364 Face face = Face.create(pointBuffer, faceIndices[i]);
365 HalfEdge he = face.he0;
366 do {
367 HalfEdge heOpp = findHalfEdge(he.head(), he.tail());
368 if (heOpp != null) {
369 he.setOpposite(heOpp);
370 }
371 he = he.next;
372 } while (he != face.he0);
373 faces.add(face);
374 }
375 }
376
377
378
379
380
381
382 public void printPoints(PrintStream ps) {
383 for (int i = 0; i < numPoints; i++) {
384 Point3d pnt = pointBuffer[i].pnt;
385 ps.println(pnt.x + ", " + pnt.y + ", " + pnt.z + ",");
386 }
387 }
388
389
390
391
392
393
394
395
396
397
398 public void build(double[] coords) throws IllegalArgumentException {
399 build(coords, coords.length / 3);
400 }
401
402
403
404
405
406
407
408
409
410
411
412
413 public void build(double[] coords, int nump) throws IllegalArgumentException {
414 if (nump < 4) {
415 throw new IllegalArgumentException("Less than four input points specified");
416 }
417 if (coords.length / 3 < nump) {
418 throw new IllegalArgumentException("Coordinate array too small for specified number of points");
419 }
420 initBuffers(nump);
421 setPoints(coords, nump);
422 buildHull();
423 }
424
425
426
427
428
429
430
431
432 public void build(Point3d[] points) throws IllegalArgumentException {
433 build(points, points.length);
434 }
435
436
437
438
439
440
441
442
443
444
445 public void build(Point3d[] points, int nump) throws IllegalArgumentException {
446 if (nump < 4) {
447 throw new IllegalArgumentException("Less than four input points specified");
448 }
449 if (points.length < nump) {
450 throw new IllegalArgumentException("Point array too small for specified number of points");
451 }
452 initBuffers(nump);
453 setPoints(points, nump);
454 buildHull();
455 }
456
457
458
459
460
461
462
463 public void triangulate() {
464 double minArea = 1000 * charLength * DOUBLE_PREC;
465 newFaces.clear();
466 for (Face face : faces) {
467 if (face.mark == Face.VISIBLE) {
468 face.triangulate(newFaces, minArea);
469
470 }
471 }
472 for (Face face = newFaces.first(); face != null; face = face.next) {
473 faces.add(face);
474 }
475 }
476
477
478
479
480
481
482
483 protected void initBuffers(int nump) {
484 if (pointBuffer.length < nump) {
485 Vertex[] newBuffer = new Vertex[nump];
486 vertexPointIndices = new int[nump];
487 System.arraycopy(pointBuffer, 0, newBuffer, 0, pointBuffer.length);
488 for (int i = pointBuffer.length; i < nump; i++) {
489 newBuffer[i] = new Vertex();
490 }
491 pointBuffer = newBuffer;
492 }
493 faces.clear();
494 claimed.clear();
495 numFaces = 0;
496 numPoints = nump;
497 }
498
499
500
501
502
503
504
505 protected void setPoints(double[] coords, int nump) {
506 for (int i = 0; i < nump; i++) {
507 Vertex vtx = pointBuffer[i];
508 vtx.pnt.set(coords[i * 3 + 0], coords[i * 3 + 1], coords[i * 3 + 2]);
509 vtx.index = i;
510 }
511 }
512
513
514
515
516
517
518
519 protected void setPoints(Point3d[] pnts, int nump) {
520 for (int i = 0; i < nump; i++) {
521 Vertex vtx = pointBuffer[i];
522 vtx.pnt.set(pnts[i]);
523 vtx.index = i;
524 }
525 }
526
527
528
529
530
531 protected void computeMaxAndMin() {
532 Vector3d max = new Vector3d();
533 Vector3d min = new Vector3d();
534
535 for (int i = 0; i < 3; i++) {
536 maxVtxs[i] = minVtxs[i] = pointBuffer[0];
537 }
538 max.set(pointBuffer[0].pnt);
539 min.set(pointBuffer[0].pnt);
540
541 for (int i = 1; i < numPoints; i++) {
542 Point3d pnt = pointBuffer[i].pnt;
543 if (pnt.x > max.x) {
544 max.x = pnt.x;
545 maxVtxs[0] = pointBuffer[i];
546 } else if (pnt.x < min.x) {
547 min.x = pnt.x;
548 minVtxs[0] = pointBuffer[i];
549 }
550 if (pnt.y > max.y) {
551 max.y = pnt.y;
552 maxVtxs[1] = pointBuffer[i];
553 } else if (pnt.y < min.y) {
554 min.y = pnt.y;
555 minVtxs[1] = pointBuffer[i];
556 }
557 if (pnt.z > max.z) {
558 max.z = pnt.z;
559 maxVtxs[2] = pointBuffer[i];
560 } else if (pnt.z < min.z) {
561 min.z = pnt.z;
562 minVtxs[2] = pointBuffer[i];
563 }
564 }
565
566
567
568 charLength = Math.max(max.x - min.x, max.y - min.y);
569 charLength = Math.max(max.z - min.z, charLength);
570 if (explicitTolerance == AUTOMATIC_TOLERANCE) {
571 tolerance =
572 3 * DOUBLE_PREC * (Math.max(Math.abs(max.x), Math.abs(min.x)) + Math.max(Math.abs(max.y), Math.abs(min.y)) + Math.max(Math.abs(max.z), Math.abs(min.z)));
573 } else {
574 tolerance = explicitTolerance;
575 }
576 }
577
578
579
580
581
582
583
584 protected void createInitialSimplex() throws IllegalArgumentException {
585 double max = 0;
586 int imax = 0;
587
588 for (int i = 0; i < 3; i++) {
589 double diff = maxVtxs[i].pnt.get(i) - minVtxs[i].pnt.get(i);
590 if (diff > max) {
591 max = diff;
592 imax = i;
593 }
594 }
595
596 if (max <= tolerance) {
597 throw new IllegalArgumentException("Input points appear to be coincident");
598 }
599 Vertex[] vtx = new Vertex[4];
600
601
602
603 vtx[0] = maxVtxs[imax];
604 vtx[1] = minVtxs[imax];
605
606
607
608 Vector3d u01 = new Vector3d();
609 Vector3d diff02 = new Vector3d();
610 Vector3d nrml = new Vector3d();
611 Vector3d xprod = new Vector3d();
612 double maxSqr = 0;
613 u01.sub(vtx[1].pnt, vtx[0].pnt);
614 u01.normalize();
615 for (int i = 0; i < numPoints; i++) {
616 diff02.sub(pointBuffer[i].pnt, vtx[0].pnt);
617 xprod.cross(u01, diff02);
618 double lenSqr = xprod.normSquared();
619 if (lenSqr > maxSqr && pointBuffer[i] != vtx[0] &&
620 pointBuffer[i] != vtx[1]) {
621 maxSqr = lenSqr;
622 vtx[2] = pointBuffer[i];
623 nrml.set(xprod);
624 }
625 }
626 if (Math.sqrt(maxSqr) <= 100 * tolerance) {
627 throw new IllegalArgumentException("Input points appear to be colinear");
628 }
629 nrml.normalize();
630
631 double maxDist = 0;
632 double d0 = vtx[2].pnt.dot(nrml);
633 for (int i = 0; i < numPoints; i++) {
634 double dist = Math.abs(pointBuffer[i].pnt.dot(nrml) - d0);
635 if (dist > maxDist && pointBuffer[i] != vtx[0] &&
636 pointBuffer[i] != vtx[1] && pointBuffer[i] != vtx[2]) {
637 maxDist = dist;
638 vtx[3] = pointBuffer[i];
639 }
640 }
641 if (Math.abs(maxDist) <= 100 * tolerance) {
642 throw new IllegalArgumentException("Input points appear to be coplanar");
643 }
644
645 if (logger.isLoggable(Level.FINE)) {
646 logger.fine("initial vertices:");
647 logger.fine(vtx[0].index + ": " + vtx[0].pnt);
648 logger.fine(vtx[1].index + ": " + vtx[1].pnt);
649 logger.fine(vtx[2].index + ": " + vtx[2].pnt);
650 logger.fine(vtx[3].index + ": " + vtx[3].pnt);
651 }
652
653 Face[] tris = new Face[4];
654
655 if (vtx[3].pnt.dot(nrml) - d0 < 0) {
656 tris[0] = Face.createTriangle(vtx[0], vtx[1], vtx[2]);
657 tris[1] = Face.createTriangle(vtx[3], vtx[1], vtx[0]);
658 tris[2] = Face.createTriangle(vtx[3], vtx[2], vtx[1]);
659 tris[3] = Face.createTriangle(vtx[3], vtx[0], vtx[2]);
660
661 for (int i = 0; i < 3; i++) {
662 int k = (i + 1) % 3;
663 tris[i + 1].getEdge(1).setOpposite(tris[k + 1].getEdge(0));
664 tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge(k));
665 }
666 } else {
667 tris[0] = Face.createTriangle(vtx[0], vtx[2], vtx[1]);
668 tris[1] = Face.createTriangle(vtx[3], vtx[0], vtx[1]);
669 tris[2] = Face.createTriangle(vtx[3], vtx[1], vtx[2]);
670 tris[3] = Face.createTriangle(vtx[3], vtx[2], vtx[0]);
671
672 for (int i = 0; i < 3; i++) {
673 int k = (i + 1) % 3;
674 tris[i + 1].getEdge(0).setOpposite(tris[k + 1].getEdge(1));
675 tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge((3 - i) % 3));
676 }
677 }
678
679 faces.addAll(Arrays.asList(tris).subList(0, 4));
680
681 for (int i = 0; i < numPoints; i++) {
682 Vertex v = pointBuffer[i];
683
684 if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3]) {
685 continue;
686 }
687
688 maxDist = tolerance;
689 Face maxFace = null;
690 for (int k = 0; k < 4; k++) {
691 double dist = tris[k].distanceToPlane(v.pnt);
692 if (dist > maxDist) {
693 maxFace = tris[k];
694 maxDist = dist;
695 }
696 }
697 if (maxFace != null) {
698 addPointToFace(v, maxFace);
699 }
700 }
701 }
702
703
704
705
706
707
708 public int getNumVertices() {
709 return numVertices;
710 }
711
712
713
714
715
716
717
718
719 public Point3d[] getVertices() {
720 Point3d[] vtxs = new Point3d[numVertices];
721 for (int i = 0; i < numVertices; i++) {
722 vtxs[i] = pointBuffer[vertexPointIndices[i]].pnt;
723 }
724 return vtxs;
725 }
726
727
728
729
730
731
732
733
734
735
736
737 public int getVertices(double[] coords) {
738 for (int i = 0; i < numVertices; i++) {
739 Point3d pnt = pointBuffer[vertexPointIndices[i]].pnt;
740 coords[i * 3 + 0] = pnt.x;
741 coords[i * 3 + 1] = pnt.y;
742 coords[i * 3 + 2] = pnt.z;
743 }
744 return numVertices;
745 }
746
747
748
749
750
751
752
753 public int[] getVertexPointIndices() {
754 int[] indices = new int[numVertices];
755 System.arraycopy(vertexPointIndices, 0, indices, 0, numVertices);
756 return indices;
757 }
758
759
760
761
762
763
764 public int getNumFaces() {
765 return faces.size();
766 }
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781 public int[][] getFaces() {
782 return getFaces(0);
783 }
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800 public int[][] getFaces(int indexFlags) {
801 int[][] allFaces = new int[faces.size()][];
802 int k = 0;
803 for (Face face : faces) {
804 allFaces[k] = new int[face.numVertices()];
805 getFaceIndices(allFaces[k], face, indexFlags);
806 k++;
807 }
808 return allFaces;
809 }
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829 public void print(PrintStream ps) {
830 print(ps, 0);
831 }
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853 public void print(PrintStream ps, int indexFlags) {
854 if ((indexFlags & INDEXED_FROM_ZERO) == 0) {
855 indexFlags |= INDEXED_FROM_ONE;
856 }
857 for (int i = 0; i < numVertices; i++) {
858 Point3d pnt = pointBuffer[vertexPointIndices[i]].pnt;
859 ps.println("v " + pnt.x + " " + pnt.y + " " + pnt.z);
860 }
861 for (Face face : faces) {
862 int[] indices = new int[face.numVertices()];
863 getFaceIndices(indices, face, indexFlags);
864
865 ps.print("f");
866 for (int index : indices) {
867 ps.print(" " + index);
868 }
869 ps.println();
870 }
871 }
872
873 private void getFaceIndices(int[] indices, Face face, int flags) {
874 boolean ccw = (flags & CLOCKWISE) == 0;
875 boolean indexedFromOne = (flags & INDEXED_FROM_ONE) != 0;
876 boolean pointRelative = (flags & POINT_RELATIVE) != 0;
877
878 HalfEdge hedge = face.he0;
879 int k = 0;
880 do {
881 int idx = hedge.head().index;
882 if (pointRelative) {
883 idx = vertexPointIndices[idx];
884 }
885 if (indexedFromOne) {
886 idx++;
887 }
888 indices[k++] = idx;
889 hedge = (ccw ? hedge.next : hedge.prev);
890 } while (hedge != face.he0);
891 }
892
893
894
895
896
897
898 protected void resolveUnclaimedPoints(FaceList newFaces) {
899 Vertex vtxNext = unclaimed.first();
900 for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) {
901 vtxNext = vtx.next;
902
903 double maxDist = tolerance;
904 Face maxFace = null;
905 for (Face newFace = newFaces.first(); newFace != null; newFace = newFace.next) {
906 if (newFace.mark == Face.VISIBLE) {
907 double dist = newFace.distanceToPlane(vtx.pnt);
908 if (dist > maxDist) {
909 maxDist = dist;
910 maxFace = newFace;
911 }
912 if (maxDist > 1000 * tolerance) {
913 break;
914 }
915 }
916 }
917 if (maxFace != null) {
918 addPointToFace(vtx, maxFace);
919 if (logger.isLoggable(Level.FINE) && vtx.index == findIndex) {
920 logger.fine(findIndex + " CLAIMED BY " + maxFace.getVertexString());
921 }
922 } else {
923 if (logger.isLoggable(Level.FINE) && vtx.index == findIndex) {
924 logger.fine(findIndex + " DISCARDED");
925 }
926 }
927 }
928 }
929
930
931
932
933
934
935
936
937 protected void deleteFacePoints(Face face, Face absorbingFace) {
938 Vertex faceVtxs = removeAllPointsFromFace(face);
939 if (faceVtxs != null) {
940 if (absorbingFace == null) {
941 unclaimed.addAll(faceVtxs);
942 } else {
943 Vertex vtxNext = faceVtxs;
944 for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) {
945 vtxNext = vtx.next;
946 double dist = absorbingFace.distanceToPlane(vtx.pnt);
947 if (dist > tolerance) {
948 addPointToFace(vtx, absorbingFace);
949 } else {
950 unclaimed.add(vtx);
951 }
952 }
953 }
954 }
955 }
956
957 private static final int NONCONVEX_WRT_LARGER_FACE = 1;
958
959 private static final int NONCONVEX = 2;
960
961
962
963
964
965
966
967
968 protected double oppFaceDistance(HalfEdge he) {
969 return he.face.distanceToPlane(he.opposite.face.getCentroid());
970 }
971
972
973
974
975
976
977
978
979
980 private boolean doAdjacentMerge(Face face, int mergeType) {
981 HalfEdge hedge = face.he0;
982
983 boolean convex = true;
984 do {
985 Face oppFace = hedge.oppositeFace();
986 boolean merge = false;
987
988 if (mergeType == NONCONVEX) {
989
990 if (oppFaceDistance(hedge) > -tolerance || oppFaceDistance(hedge.opposite) > -tolerance) {
991 merge = true;
992 }
993 } else {
994
995
996
997
998 if (face.area > oppFace.area) {
999 if (oppFaceDistance(hedge) > -tolerance) {
1000 merge = true;
1001 } else if (oppFaceDistance(hedge.opposite) > -tolerance) {
1002 convex = false;
1003 }
1004 } else {
1005 if (oppFaceDistance(hedge.opposite) > -tolerance) {
1006 merge = true;
1007 } else if (oppFaceDistance(hedge) > -tolerance) {
1008 convex = false;
1009 }
1010 }
1011 }
1012
1013 if (merge) {
1014 if (logger.isLoggable(Level.FINE)) {
1015 logger.fine(" merging " + face.getVertexString() + " and " + oppFace.getVertexString());
1016 }
1017
1018 int numd = face.mergeAdjacentFace(hedge, discardedFaces);
1019 for (int i = 0; i < numd; i++) {
1020 deleteFacePoints(discardedFaces[i], face);
1021 }
1022 if (logger.isLoggable(Level.FINE)) {
1023 logger.fine(" result: " + face.getVertexString());
1024 }
1025 return true;
1026 }
1027 hedge = hedge.next;
1028 } while (hedge != face.he0);
1029 if (!convex) {
1030 face.mark = Face.NON_CONVEX;
1031 }
1032 return false;
1033 }
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044 protected void calculateHorizon(Point3d eyePnt, HalfEdge edge0, Face face, Vector<HalfEdge> horizon) {
1045
1046 deleteFacePoints(face, null);
1047 face.mark = Face.DELETED;
1048 if (logger.isLoggable(Level.FINE)) {
1049 logger.fine(" visiting face " + face.getVertexString());
1050 }
1051 HalfEdge edge;
1052 if (edge0 == null) {
1053 edge0 = face.getEdge(0);
1054 edge = edge0;
1055 } else {
1056 edge = edge0.getNext();
1057 }
1058 do {
1059 Face oppFace = edge.oppositeFace();
1060 if (oppFace.mark == Face.VISIBLE) {
1061 if (oppFace.distanceToPlane(eyePnt) > tolerance) {
1062 calculateHorizon(eyePnt, edge.getOpposite(), oppFace, horizon);
1063 } else {
1064 horizon.add(edge);
1065 if (logger.isLoggable(Level.FINE)) {
1066 logger.fine(" adding horizon edge " + edge.getVertexString());
1067 }
1068 }
1069 }
1070 edge = edge.getNext();
1071 } while (edge != edge0);
1072 }
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082 private HalfEdge addAdjoiningFace(Vertex eyeVtx, HalfEdge he) {
1083 Face face = Face.createTriangle(eyeVtx, he.tail(), he.head());
1084 faces.add(face);
1085 face.getEdge(-1).setOpposite(he.getOpposite());
1086 return face.getEdge(0);
1087 }
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 protected void addNewFaces(FaceList newFaces, Vertex eyeVtx, Vector<HalfEdge> horizon) {
1098 newFaces.clear();
1099
1100 HalfEdge hedgeSidePrev = null;
1101 HalfEdge hedgeSideBegin = null;
1102
1103 for (HalfEdge horizonHe : horizon) {
1104 HalfEdge hedgeSide = addAdjoiningFace(eyeVtx, horizonHe);
1105 if (logger.isLoggable(Level.FINE)) {
1106 logger.fine("new face: " + hedgeSide.face.getVertexString());
1107 }
1108 if (hedgeSidePrev != null) {
1109 hedgeSide.next.setOpposite(hedgeSidePrev);
1110 } else {
1111 hedgeSideBegin = hedgeSide;
1112 }
1113 newFaces.add(hedgeSide.getFace());
1114 hedgeSidePrev = hedgeSide;
1115 }
1116 hedgeSideBegin.next.setOpposite(hedgeSidePrev);
1117 }
1118
1119
1120
1121
1122
1123
1124
1125 protected Vertex nextPointToAdd() {
1126 if (!claimed.isEmpty()) {
1127 Face eyeFace = claimed.first().face;
1128 Vertex eyeVtx = null;
1129 double maxDist = 0;
1130 for (Vertex vtx = eyeFace.outside; vtx != null && vtx.face == eyeFace; vtx = vtx.next) {
1131 double dist = eyeFace.distanceToPlane(vtx.pnt);
1132 if (dist > maxDist) {
1133 maxDist = dist;
1134 eyeVtx = vtx;
1135 }
1136 }
1137 return eyeVtx;
1138 } else {
1139 return null;
1140 }
1141 }
1142
1143
1144
1145
1146
1147
1148
1149 protected void addPointToHull(Vertex eyeVtx) {
1150 horizon.clear();
1151 unclaimed.clear();
1152
1153 if (logger.isLoggable(Level.FINE)) {
1154 logger.fine("Adding point: " + eyeVtx.index);
1155 logger.fine(" which is " + eyeVtx.face.distanceToPlane(eyeVtx.pnt) + " above face " + eyeVtx.face.getVertexString());
1156 }
1157 removePointFromFace(eyeVtx, eyeVtx.face);
1158 calculateHorizon(eyeVtx.pnt, null, eyeVtx.face, horizon);
1159 newFaces.clear();
1160 addNewFaces(newFaces, eyeVtx, horizon);
1161
1162
1163
1164
1165 for (Face face = newFaces.first(); face != null; face = face.next) {
1166 if (face.mark == Face.VISIBLE) {
1167 while (doAdjacentMerge(face, NONCONVEX_WRT_LARGER_FACE)) {
1168
1169 }
1170 }
1171 }
1172
1173
1174 for (Face face = newFaces.first(); face != null; face = face.next) {
1175 if (face.mark == Face.NON_CONVEX) {
1176 face.mark = Face.VISIBLE;
1177 while (doAdjacentMerge(face, NONCONVEX)) {
1178
1179 }
1180 }
1181 }
1182 resolveUnclaimedPoints(newFaces);
1183 }
1184
1185
1186
1187
1188
1189 protected void buildHull() {
1190 int cnt = 0;
1191 Vertex eyeVtx;
1192
1193 computeMaxAndMin();
1194 createInitialSimplex();
1195 while ((eyeVtx = nextPointToAdd()) != null) {
1196 addPointToHull(eyeVtx);
1197 cnt++;
1198 logger.fine("iteration " + cnt + " done");
1199 }
1200 reindexFacesAndVertices();
1201 logger.fine("hull done");
1202 }
1203
1204
1205
1206
1207
1208
1209
1210 private void markFaceVertices(Face face, int mark) {
1211 HalfEdge he0 = face.getFirstEdge();
1212 HalfEdge he = he0;
1213 do {
1214 he.head().index = mark;
1215 he = he.next;
1216 } while (he != he0);
1217 }
1218
1219
1220
1221
1222
1223 protected void reindexFacesAndVertices() {
1224 for (int i = 0; i < numPoints; i++) {
1225 pointBuffer[i].index = -1;
1226 }
1227
1228 numFaces = 0;
1229 for (Iterator<Face> it = faces.iterator(); it.hasNext(); ) {
1230 Face face = it.next();
1231 if (face.mark != Face.VISIBLE) {
1232 it.remove();
1233 } else {
1234 markFaceVertices(face, 0);
1235 numFaces++;
1236 }
1237 }
1238
1239 numVertices = 0;
1240 for (int i = 0; i < numPoints; i++) {
1241 Vertex vtx = pointBuffer[i];
1242 if (vtx.index == 0) {
1243 vertexPointIndices[numVertices] = i;
1244 vtx.index = numVertices++;
1245 }
1246 }
1247 }
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258 protected boolean checkFaceConvexity(Face face, double tol, PrintStream ps) {
1259 double dist;
1260 HalfEdge he = face.he0;
1261 do {
1262 face.checkConsistency();
1263
1264 dist = oppFaceDistance(he);
1265 if (dist > tol) {
1266 if (ps != null) {
1267 ps.println("Edge " + he.getVertexString() + " non-convex by " + dist);
1268 }
1269 return false;
1270 }
1271 dist = oppFaceDistance(he.opposite);
1272 if (dist > tol) {
1273 if (ps != null) {
1274 ps.println("Opposite edge " + he.opposite.getVertexString() + " non-convex by " + dist);
1275 }
1276 return false;
1277 }
1278 if (he.next.oppositeFace() == he.oppositeFace()) {
1279 if (ps != null) {
1280 ps.println("Redundant vertex " + he.head().index + " in face " + face.getVertexString());
1281 }
1282 return false;
1283 }
1284 he = he.next;
1285 } while (he != face.he0);
1286 return true;
1287 }
1288
1289
1290
1291
1292
1293
1294
1295
1296 protected boolean checkFaces(double tol, PrintStream ps) {
1297
1298 boolean convex = true;
1299 for (Face face : faces) {
1300 if (face.mark == Face.VISIBLE && !checkFaceConvexity(face, tol, ps)) {
1301 convex = false;
1302 }
1303 }
1304 return convex;
1305 }
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318 public boolean check(PrintStream ps) {
1319 return check(ps, getDistanceTolerance());
1320 }
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341 public boolean check(PrintStream ps, double tol) {
1342
1343
1344 double dist;
1345 double pointTol = 10 * tol;
1346
1347 if (!checkFaces(tolerance, ps)) {
1348 return false;
1349 }
1350
1351
1352
1353 for (int i = 0; i < numPoints; i++) {
1354 Point3d pnt = pointBuffer[i].pnt;
1355 for (Face face : faces) {
1356 if (face.mark == Face.VISIBLE) {
1357 dist = face.distanceToPlane(pnt);
1358 if (dist > pointTol) {
1359 if (ps != null) {
1360 ps.println("Point " + i + " " + dist + " above face " + face.getVertexString());
1361 }
1362 return false;
1363 }
1364 }
1365 }
1366 }
1367 return true;
1368 }
1369 }