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
41
42
43
44
45
46
47
48
49
50
51 public class Face {
52
53 protected static final int DELETED = 3;
54
55 protected static final int NON_CONVEX = 2;
56
57 protected static final int VISIBLE = 1;
58
59 protected double area;
60
61 protected HalfEdge he0;
62
63 protected int mark;
64
65 protected Face next;
66
67 protected int numVerts;
68
69 protected Vertex outside;
70
71 protected double planeOffset;
72
73 private final Point3d centroid;
74
75 private final Vector3d normal;
76
77 public Face() {
78 normal = new Vector3d();
79 centroid = new Point3d();
80 mark = VISIBLE;
81 }
82
83
84
85
86
87
88
89
90
91 public static Face create(Vertex[] vtxArray, int[] indices) {
92 Face face = new Face();
93 HalfEdge hePrev = null;
94 for (int index : indices) {
95 HalfEdge he = new HalfEdge(vtxArray[index], face);
96 if (hePrev != null) {
97 he.setPrev(hePrev);
98 hePrev.setNext(he);
99 } else {
100 face.he0 = he;
101 }
102 hePrev = he;
103 }
104 face.he0.setPrev(hePrev);
105 hePrev.setNext(face.he0);
106
107
108 face.computeNormalAndCentroid();
109 return face;
110 }
111
112
113
114
115
116
117
118
119
120 public static Face createTriangle(Vertex v0, Vertex v1, Vertex v2) {
121 return createTriangle(v0, v1, v2, 0);
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135 public static Face createTriangle(Vertex v0, Vertex v1, Vertex v2, double minArea) {
136 Face face = new Face();
137 HalfEdge he0 = new HalfEdge(v0, face);
138 HalfEdge he1 = new HalfEdge(v1, face);
139 HalfEdge he2 = new HalfEdge(v2, face);
140
141 he0.prev = he2;
142 he0.next = he1;
143 he1.prev = he0;
144 he1.next = he2;
145 he2.prev = he1;
146 he2.next = he0;
147
148 face.he0 = he0;
149
150
151 face.computeNormalAndCentroid(minArea);
152 return face;
153 }
154
155
156
157
158
159
160
161 public void computeCentroid(Point3d centroid) {
162 centroid.setZero();
163 HalfEdge he = he0;
164 do {
165 centroid.add(he.head().pnt);
166 he = he.next;
167 } while (he != he0);
168 centroid.scale(1 / (double) numVerts);
169 }
170
171
172
173
174
175
176
177
178 public void computeNormal(Vector3d normal) {
179 HalfEdge he1 = he0.next;
180 HalfEdge he2 = he1.next;
181
182 Point3d p0 = he0.head().pnt;
183 Point3d p2 = he1.head().pnt;
184
185 double d2x = p2.x - p0.x;
186 double d2y = p2.y - p0.y;
187 double d2z = p2.z - p0.z;
188
189 normal.setZero();
190
191 numVerts = 2;
192
193 while (he2 != he0) {
194 double d1x = d2x;
195 double d1y = d2y;
196 double d1z = d2z;
197
198 p2 = he2.head().pnt;
199 d2x = p2.x - p0.x;
200 d2y = p2.y - p0.y;
201 d2z = p2.z - p0.z;
202
203 normal.x += d1y * d2z - d1z * d2y;
204 normal.y += d1z * d2x - d1x * d2z;
205 normal.z += d1x * d2y - d1y * d2x;
206
207 he1 = he2;
208 he2 = he2.next;
209 numVerts++;
210 }
211 area = normal.norm();
212 normal.scale(1 / area);
213 }
214
215
216
217
218
219
220
221
222
223 public void computeNormal(Vector3d normal, double minArea) {
224 computeNormal(normal);
225
226 if (area < minArea) {
227
228
229
230 HalfEdge hedgeMax = null;
231 double lenSqrMax = 0;
232 HalfEdge hedge = he0;
233 do {
234 double lenSqr = hedge.lengthSquared();
235 if (lenSqr > lenSqrMax) {
236 hedgeMax = hedge;
237 lenSqrMax = lenSqr;
238 }
239 hedge = hedge.next;
240 } while (hedge != he0);
241
242 Point3d p2 = hedgeMax.head().pnt;
243 Point3d p1 = hedgeMax.tail().pnt;
244 double lenMax = Math.sqrt(lenSqrMax);
245 double ux = (p2.x - p1.x) / lenMax;
246 double uy = (p2.y - p1.y) / lenMax;
247 double uz = (p2.z - p1.z) / lenMax;
248 double dot = normal.x * ux + normal.y * uy + normal.z * uz;
249 normal.x -= dot * ux;
250 normal.y -= dot * uy;
251 normal.z -= dot * uz;
252
253 normal.normalize();
254 }
255 }
256
257
258
259
260
261
262
263 public double distanceToPlane(Point3d p) {
264 return normal.x * p.x + normal.y * p.y + normal.z * p.z - planeOffset;
265 }
266
267
268
269
270
271
272
273
274
275 public HalfEdge findEdge(Vertex vt, Vertex vh) {
276 HalfEdge he = he0;
277 do {
278 if (he.head() == vh && he.tail() == vt) {
279 return he;
280 }
281 he = he.next;
282 } while (he != he0);
283 return null;
284 }
285
286
287
288
289
290
291 public Point3d getCentroid() {
292 return centroid;
293 }
294
295
296
297
298
299
300
301 public HalfEdge getEdge(int i) {
302 HalfEdge he = he0;
303 while (i > 0) {
304 he = he.next;
305 i--;
306 }
307 while (i < 0) {
308 he = he.prev;
309 i++;
310 }
311 return he;
312 }
313
314
315
316
317
318
319 public HalfEdge getFirstEdge() {
320 return he0;
321 }
322
323
324
325
326
327
328 public Vector3d getNormal() {
329 return normal;
330 }
331
332
333
334
335
336
337
338 public void getVertexIndices(int[] idxs) {
339 HalfEdge he = he0;
340 int i = 0;
341 do {
342 idxs[i++] = he.head().index;
343 he = he.next;
344 } while (he != he0);
345 }
346
347
348
349
350
351
352
353 public String getVertexString() {
354 StringBuilder s = null;
355 HalfEdge he = he0;
356 do {
357 if (s == null) {
358 s = new StringBuilder("" + he.head().index);
359 } else {
360 s.append(" ").append(he.head().index);
361 }
362 he = he.next;
363 } while (he != he0);
364 return s.toString();
365 }
366
367
368
369
370
371
372
373
374
375
376 public int mergeAdjacentFace(HalfEdge hedgeAdj, Face[] discarded) {
377 Face oppFace = hedgeAdj.oppositeFace();
378 int numDiscarded = 0;
379
380 discarded[numDiscarded++] = oppFace;
381 oppFace.mark = DELETED;
382
383 HalfEdge hedgeOpp = hedgeAdj.getOpposite();
384
385 HalfEdge hedgeAdjPrev = hedgeAdj.prev;
386 HalfEdge hedgeAdjNext = hedgeAdj.next;
387 HalfEdge hedgeOppPrev = hedgeOpp.prev;
388 HalfEdge hedgeOppNext = hedgeOpp.next;
389
390 while (hedgeAdjPrev.oppositeFace() == oppFace) {
391 hedgeAdjPrev = hedgeAdjPrev.prev;
392 hedgeOppNext = hedgeOppNext.next;
393 }
394
395 while (hedgeAdjNext.oppositeFace() == oppFace) {
396 hedgeOppPrev = hedgeOppPrev.prev;
397 hedgeAdjNext = hedgeAdjNext.next;
398 }
399
400 HalfEdge hedge;
401
402 for (hedge = hedgeOppNext; hedge != hedgeOppPrev.next; hedge = hedge.next) {
403 hedge.face = this;
404 }
405
406 if (hedgeAdj == he0) {
407 he0 = hedgeAdjNext;
408 }
409
410
411 Face discardedFace;
412
413 discardedFace = connectHalfEdges(hedgeOppPrev, hedgeAdjNext);
414 if (discardedFace != null) {
415 discarded[numDiscarded++] = discardedFace;
416 }
417
418
419 discardedFace = connectHalfEdges(hedgeAdjPrev, hedgeOppNext);
420 if (discardedFace != null) {
421 discarded[numDiscarded++] = discardedFace;
422 }
423
424 computeNormalAndCentroid();
425 checkConsistency();
426
427 return numDiscarded;
428 }
429
430
431
432
433
434
435 public int numVertices() {
436 return numVerts;
437 }
438
439
440
441
442
443
444
445
446
447 public void triangulate(FaceList newFaces, double minArea) {
448 HalfEdge hedge;
449
450 if (numVertices() < 4) {
451 return;
452 }
453
454 Vertex v0 = he0.head();
455
456 hedge = he0.next;
457 HalfEdge oppPrev = hedge.opposite;
458 Face face0 = null;
459
460 for (hedge = hedge.next; hedge != he0.prev; hedge = hedge.next) {
461 Face face = createTriangle(v0, hedge.prev.head(), hedge.head(), minArea);
462 face.he0.next.setOpposite(oppPrev);
463 face.he0.prev.setOpposite(hedge.opposite);
464 oppPrev = face.he0;
465 newFaces.add(face);
466 if (face0 == null) {
467 face0 = face;
468 }
469 }
470 hedge = new HalfEdge(he0.prev.prev.head(), this);
471 hedge.setOpposite(oppPrev);
472
473 hedge.prev = he0;
474 hedge.prev.next = hedge;
475
476 hedge.next = he0.prev;
477 hedge.next.prev = hedge;
478
479 computeNormalAndCentroid(minArea);
480 checkConsistency();
481
482 for (Face face = face0; face != null; face = face.next) {
483 face.checkConsistency();
484 }
485
486 }
487
488
489
490
491
492
493
494
495
496
497 public double areaSquared(HalfEdge hedge0, HalfEdge hedge1) {
498 Point3d p0 = hedge0.tail().pnt;
499 Point3d p1 = hedge0.head().pnt;
500 Point3d p2 = hedge1.head().pnt;
501
502 double dx1 = p1.x - p0.x;
503 double dy1 = p1.y - p0.y;
504 double dz1 = p1.z - p0.z;
505
506 double dx2 = p2.x - p0.x;
507 double dy2 = p2.y - p0.y;
508 double dz2 = p2.z - p0.z;
509
510 double x = dy1 * dz2 - dz1 * dy2;
511 double y = dz1 * dx2 - dx1 * dz2;
512 double z = dx1 * dy2 - dy1 * dx2;
513
514 return x * x + y * y + z * z;
515 }
516
517 private void computeNormalAndCentroid() {
518 computeNormal(normal);
519 computeCentroid(centroid);
520 planeOffset = normal.dot(centroid);
521 int numv = 0;
522 HalfEdge he = he0;
523 do {
524 numv++;
525 he = he.next;
526 } while (he != he0);
527 if (numv != numVerts) {
528 throw new InternalErrorException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv);
529 }
530 }
531
532 private void computeNormalAndCentroid(double minArea) {
533 computeNormal(normal, minArea);
534 computeCentroid(centroid);
535 planeOffset = normal.dot(centroid);
536 }
537
538 private Face connectHalfEdges(HalfEdge hedgePrev, HalfEdge hedge) {
539 Face discardedFace = null;
540
541 if (hedgePrev.oppositeFace() == hedge.oppositeFace()) {
542
543
544
545
546
547 Face oppFace = hedge.oppositeFace();
548 HalfEdge hedgeOpp;
549
550 if (hedgePrev == he0) {
551 he0 = hedge;
552 }
553 if (oppFace.numVertices() == 3) {
554
555 hedgeOpp = hedge.getOpposite().prev.getOpposite();
556
557 oppFace.mark = DELETED;
558 discardedFace = oppFace;
559 } else {
560 hedgeOpp = hedge.getOpposite().next;
561
562 if (oppFace.he0 == hedgeOpp.prev) {
563 oppFace.he0 = hedgeOpp;
564 }
565 hedgeOpp.prev = hedgeOpp.prev.prev;
566 hedgeOpp.prev.next = hedgeOpp;
567 }
568 hedge.prev = hedgePrev.prev;
569 hedge.prev.next = hedge;
570
571 hedge.opposite = hedgeOpp;
572 hedgeOpp.opposite = hedge;
573
574
575 oppFace.computeNormalAndCentroid();
576 } else {
577 hedgePrev.next = hedge;
578 hedge.prev = hedgePrev;
579 }
580 return discardedFace;
581 }
582
583
584
585
586
587
588
589 void checkConsistency() {
590
591 HalfEdge hedge = he0;
592 double maxd = 0;
593 int numv = 0;
594
595 if (numVerts < 3) {
596 throw new InternalErrorException("degenerate face: " + getVertexString());
597 }
598 do {
599 HalfEdge hedgeOpp = hedge.getOpposite();
600 if (hedgeOpp == null) {
601 throw new InternalErrorException("face " + getVertexString() + ": " + "unreflected half edge " + hedge.getVertexString());
602 } else if (hedgeOpp.getOpposite() != hedge) {
603 throw new InternalErrorException("face " + getVertexString() + ": " + "opposite half edge " + hedgeOpp.getVertexString() + " has opposite "
604 + hedgeOpp.getOpposite().getVertexString());
605 }
606 if (hedgeOpp.head() != hedge.tail() || hedge.head() != hedgeOpp.tail()) {
607 throw new InternalErrorException("face " + getVertexString() + ": " + "half edge " + hedge.getVertexString() + " reflected by " + hedgeOpp.getVertexString());
608 }
609 Face oppFace = hedgeOpp.face;
610 if (oppFace == null) {
611 throw new InternalErrorException("face " + getVertexString() + ": " + "no face on half edge " + hedgeOpp.getVertexString());
612 } else if (oppFace.mark == DELETED) {
613 throw new InternalErrorException("face " + getVertexString() + ": " + "opposite face " + oppFace.getVertexString() + " not on hull");
614 }
615 double d = Math.abs(distanceToPlane(hedge.head().pnt));
616 if (d > maxd) {
617 maxd = d;
618 }
619 numv++;
620 hedge = hedge.next;
621 } while (hedge != he0);
622
623 if (numv != numVerts) {
624 throw new InternalErrorException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv);
625 }
626
627 }
628 }